diff --git a/wizard-work/sol/sol-profile-file-class-2026-10-27.json b/wizard-work/sol/sol-profile-file-class-2026-10-27.json new file mode 100644 index 0000000..4252fd3 --- /dev/null +++ b/wizard-work/sol/sol-profile-file-class-2026-10-27.json @@ -0,0 +1,416 @@ +{ + "artifact": "sol-profile-file-class-2026-10-27.json", + "profile": "/root/.hermes/profiles/sol", + "generated_utc": "2026-10-27T11:35:14Z", + "files": 36, + "dirs": 27, + "total_entries": 63, + "classes": { + "BACKUP::DIR": 1, + "BIN::DIR": 1, + "CACHE::DIR": 4, + "CACHE_ETAG::FILE": 1, + "CHECK_DIR::DIR": 1, + "CONFIG_JSON::FILE": 3, + "CONFIG_YAML::FILE": 2, + "DIR::UNCLASSIFIED": 1, + "DOC::FILE": 1, + "FILE::UNCLASSIFIED": 3, + "FORGE::DIR": 1, + "HOME_DIR::DIR": 1, + "HOOK::DIR": 1, + "LOCK::FILE": 2, + "LOG_DIR::DIR": 1, + "LSP::DIR": 1, + "MEMORY::DIR": 1, + "OUTPUT_DIR::DIR": 1, + "PAIR_PLAT::DIR": 2, + "PLUGIN_DIR::DIR": 2, + "RUNTIME_DIR::DIR": 5, + "SCRIPT_PY::FILE": 18, + "SKILL_CONTAINER::DIR": 1, + "SKIN::DIR": 1, + "STATE_DB::FILE": 6, + "VAULT::DIR": 1 + }, + "entries": [ + { + "name": ".env", + "kind": "file", + "class": "FILE::UNCLASSIFIED", + "size": 165 + }, + { + "name": ".env.bak-20261014T165715Z", + "kind": "file", + "class": "FILE::UNCLASSIFIED", + "size": 165 + }, + { + "name": ".env.lock", + "kind": "file", + "class": "LOCK::FILE", + "size": 0 + }, + { + "name": ".skills_prompt_snapshot.json", + "kind": "file", + "class": "CONFIG_JSON::FILE", + "size": 42870 + }, + { + "name": "SOUL.md", + "kind": "file", + "class": "DOC::FILE", + "size": 685 + }, + { + "name": "_check_repo_json.py", + "kind": "file", + "class": "SCRIPT_PY::FILE", + "size": 515 + }, + { + "name": "_gen_daily_ledger_json.py", + "kind": "file", + "class": "SCRIPT_PY::FILE", + "size": 2414 + }, + { + "name": "_gen_daily_ledger_report.py", + "kind": "file", + "class": "SCRIPT_PY::FILE", + "size": 3340 + }, + { + "name": "_gen_daily_triage.py", + "kind": "file", + "class": "SCRIPT_PY::FILE", + "size": 2012 + }, + { + "name": "_gen_daily_triage_fresh.py", + "kind": "file", + "class": "SCRIPT_PY::FILE", + "size": 1475 + }, + { + "name": "_verify_daily_triage.py", + "kind": "file", + "class": "SCRIPT_PY::FILE", + "size": 2738 + }, + { + "name": "audio_cache", + "kind": "dir", + "class": "CACHE::DIR", + "size": 4096 + }, + { + "name": "auth.json", + "kind": "file", + "class": "CONFIG_JSON::FILE", + "size": 143 + }, + { + "name": "auth.lock", + "kind": "file", + "class": "LOCK::FILE", + "size": 0 + }, + { + "name": "backups", + "kind": "dir", + "class": "BACKUP::DIR", + "size": 4096 + }, + { + "name": "bin", + "kind": "dir", + "class": "BIN::DIR", + "size": 4096 + }, + { + "name": "cache", + "kind": "dir", + "class": "CACHE::DIR", + "size": 4096 + }, + { + "name": "config.yaml", + "kind": "file", + "class": "CONFIG_YAML::FILE", + "size": 510 + }, + { + "name": "config.yaml.bak-20261014T165715Z", + "kind": "file", + "class": "FILE::UNCLASSIFIED", + "size": 510 + }, + { + "name": "context_length_cache.yaml", + "kind": "file", + "class": "CONFIG_YAML::FILE", + "size": 94 + }, + { + "name": "cron", + "kind": "dir", + "class": "RUNTIME_DIR::DIR", + "size": 4096 + }, + { + "name": "cron-fleet-triage-2026-10-17.py", + "kind": "file", + "class": "SCRIPT_PY::FILE", + "size": 5407 + }, + { + "name": "cron-fleet-triage-2026-10-18.py", + "kind": "file", + "class": "SCRIPT_PY::FILE", + "size": 5355 + }, + { + "name": "cron-fleet-verify.py", + "kind": "file", + "class": "SCRIPT_PY::FILE", + "size": 1790 + }, + { + "name": "diag.py", + "kind": "file", + "class": "SCRIPT_PY::FILE", + "size": 1182 + }, + { + "name": "diag2.py", + "kind": "file", + "class": "SCRIPT_PY::FILE", + "size": 550 + }, + { + "name": "diag3.py", + "kind": "file", + "class": "SCRIPT_PY::FILE", + "size": 1401 + }, + { + "name": "diag4.py", + "kind": "file", + "class": "SCRIPT_PY::FILE", + "size": 1000 + }, + { + "name": "forge", + "kind": "dir", + "class": "FORGE::DIR", + "size": 4096 + }, + { + "name": "home", + "kind": "dir", + "class": "HOME_DIR::DIR", + "size": 4096 + }, + { + "name": "hooks", + "kind": "dir", + "class": "HOOK::DIR", + "size": 4096 + }, + { + "name": "image_cache", + "kind": "dir", + "class": "CACHE::DIR", + "size": 4096 + }, + { + "name": "inventory_skills.py", + "kind": "file", + "class": "SCRIPT_PY::FILE", + "size": 6472 + }, + { + "name": "keys", + "kind": "dir", + "class": "CACHE::DIR", + "size": 4096 + }, + { + "name": "logs", + "kind": "dir", + "class": "LOG_DIR::DIR", + "size": 4096 + }, + { + "name": "lsp", + "kind": "dir", + "class": "LSP::DIR", + "size": 4096 + }, + { + "name": "memories", + "kind": "dir", + "class": "MEMORY::DIR", + "size": 4096 + }, + { + "name": "models_dev_cache.etag", + "kind": "file", + "class": "CACHE_ETAG::FILE", + "size": 36 + }, + { + "name": "models_dev_cache.json", + "kind": "file", + "class": "CONFIG_JSON::FILE", + "size": 5956665 + }, + { + "name": "pairing", + "kind": "dir", + "class": "PAIR_PLAT::DIR", + "size": 4096 + }, + { + "name": "plans", + "kind": "dir", + "class": "DIR::UNCLASSIFIED", + "size": 4096 + }, + { + "name": "platforms", + "kind": "dir", + "class": "PAIR_PLAT::DIR", + "size": 4096 + }, + { + "name": "plugin-update-checks", + "kind": "dir", + "class": "PLUGIN_DIR::DIR", + "size": 4096 + }, + { + "name": "plugins", + "kind": "dir", + "class": "PLUGIN_DIR::DIR", + "size": 4096 + }, + { + "name": "runtime", + "kind": "dir", + "class": "RUNTIME_DIR::DIR", + "size": 4096 + }, + { + "name": "sandboxes", + "kind": "dir", + "class": "RUNTIME_DIR::DIR", + "size": 4096 + }, + { + "name": "sessions", + "kind": "dir", + "class": "RUNTIME_DIR::DIR", + "size": 4096 + }, + { + "name": "skills", + "kind": "dir", + "class": "SKILL_CONTAINER::DIR", + "size": 4096 + }, + { + "name": "skins", + "kind": "dir", + "class": "SKIN::DIR", + "size": 4096 + }, + { + "name": "source-checks", + "kind": "dir", + "class": "CHECK_DIR::DIR", + "size": 4096 + }, + { + "name": "state.db", + "kind": "file", + "class": "STATE_DB::FILE", + "size": 71213056 + }, + { + "name": "state.db-shm", + "kind": "file", + "class": "STATE_DB::FILE", + "size": 32768 + }, + { + "name": "state.db-wal", + "kind": "file", + "class": "STATE_DB::FILE", + "size": 2941712 + }, + { + "name": "state.db.auto-maintenance.lock", + "kind": "file", + "class": "STATE_DB::FILE", + "size": 0 + }, + { + "name": "state.db.fts_rebuild.lock", + "kind": "file", + "class": "STATE_DB::FILE", + "size": 0 + }, + { + "name": "state.db.quarantine.lock", + "kind": "file", + "class": "STATE_DB::FILE", + "size": 0 + }, + { + "name": "triage_check.py", + "kind": "file", + "class": "SCRIPT_PY::FILE", + "size": 3732 + }, + { + "name": "triage_profiles.py", + "kind": "file", + "class": "SCRIPT_PY::FILE", + "size": 4093 + }, + { + "name": "triage_skill_report.py", + "kind": "file", + "class": "SCRIPT_PY::FILE", + "size": 6354 + }, + { + "name": "vault", + "kind": "dir", + "class": "VAULT::DIR", + "size": 4096 + }, + { + "name": "verify_skills.py", + "kind": "file", + "class": "SCRIPT_PY::FILE", + "size": 1442 + }, + { + "name": "wizard-outputs", + "kind": "dir", + "class": "OUTPUT_DIR::DIR", + "size": 4096 + }, + { + "name": "workspace", + "kind": "dir", + "class": "RUNTIME_DIR::DIR", + "size": 4096 + } + ] +} \ No newline at end of file diff --git a/wizard-work/sol/sol-profile-file-class-2026-10-27.md b/wizard-work/sol/sol-profile-file-class-2026-10-27.md new file mode 100644 index 0000000..85177fd --- /dev/null +++ b/wizard-work/sol/sol-profile-file-class-2026-10-27.md @@ -0,0 +1,115 @@ +# Sol profile — file & directory class triage + +Date: 2026-10-27 | Evidence: `os.stat` of every direct entry of `/root/.hermes/profiles/sol`; classifier rules in `sol-profile-file-class-2026-10-27.py`. + +## Summary + +| Metric | Count | +|---|---| +| Total direct entries | 63 | +| Files | 36 | +| Direct dirs | 27 | + +## Class counts + +| class | count | +|---|---| +| BACKUP::DIR | 1 | +| BIN::DIR | 1 | +| CACHE::DIR | 4 | +| CACHE_ETAG::FILE | 1 | +| CHECK_DIR::DIR | 1 | +| CONFIG_JSON::FILE | 3 | +| CONFIG_YAML::FILE | 2 | +| DIR::UNCLASSIFIED | 1 | +| DOC::FILE | 1 | +| FILE::UNCLASSIFIED | 3 | +| FORGE::DIR | 1 | +| HOME_DIR::DIR | 1 | +| HOOK::DIR | 1 | +| LOCK::FILE | 2 | +| LOG_DIR::DIR | 1 | +| LSP::DIR | 1 | +| MEMORY::DIR | 1 | +| OUTPUT_DIR::DIR | 1 | +| PAIR_PLAT::DIR | 2 | +| PLUGIN_DIR::DIR | 2 | +| RUNTIME_DIR::DIR | 5 | +| SCRIPT_PY::FILE | 18 | +| SKILL_CONTAINER::DIR | 1 | +| SKIN::DIR | 1 | +| STATE_DB::FILE | 6 | +| VAULT::DIR | 1 | + +## Findings + +1. **Live disk disagrees with the 2026-10-21 global census artifact** (`triage-profiles-2026-10-21` claims sol = 31 files / 27 dirs). Re-deriving from disk gives **36 files / 27 dirs**. Upstream artifacts should be re-derived from disk on each triage pass. +2. `state.db*` family: 1 STATE_DB (.db), 2 unclassified (-shm, -wal), and 3 LOCK (-*.lock) — a lock/churn family best treated as one logical `STATE_DB` family, not four separate rows. + +## File+dir rows + +| name | kind | class | size_bytes | +|---|---|---|---| +| .env | file | FILE → UNCLASSIFIED | 165 | +| .env.bak-20261014T165715Z | file | FILE → UNCLASSIFIED | 165 | +| .env.lock | file | LOCK → FILE | 0 | +| .skills_prompt_snapshot.json | file | CONFIG_JSON → FILE | 42870 | +| SOUL.md | file | DOC → FILE | 685 | +| _check_repo_json.py | file | SCRIPT_PY → FILE | 515 | +| _gen_daily_ledger_json.py | file | SCRIPT_PY → FILE | 2414 | +| _gen_daily_ledger_report.py | file | SCRIPT_PY → FILE | 3340 | +| _gen_daily_triage.py | file | SCRIPT_PY → FILE | 2012 | +| _gen_daily_triage_fresh.py | file | SCRIPT_PY → FILE | 1475 | +| _verify_daily_triage.py | file | SCRIPT_PY → FILE | 2738 | +| audio_cache | dir | CACHE → DIR | 4096 | +| auth.json | file | CONFIG_JSON → FILE | 143 | +| auth.lock | file | LOCK → FILE | 0 | +| backups | dir | BACKUP → DIR | 4096 | +| bin | dir | BIN → DIR | 4096 | +| cache | dir | CACHE → DIR | 4096 | +| config.yaml | file | CONFIG_YAML → FILE | 510 | +| config.yaml.bak-20261014T165715Z | file | FILE → UNCLASSIFIED | 510 | +| context_length_cache.yaml | file | CONFIG_YAML → FILE | 94 | +| cron | dir | RUNTIME_DIR → DIR | 4096 | +| cron-fleet-triage-2026-10-17.py | file | SCRIPT_PY → FILE | 5407 | +| cron-fleet-triage-2026-10-18.py | file | SCRIPT_PY → FILE | 5355 | +| cron-fleet-verify.py | file | SCRIPT_PY → FILE | 1790 | +| diag.py | file | SCRIPT_PY → FILE | 1182 | +| diag2.py | file | SCRIPT_PY → FILE | 550 | +| diag3.py | file | SCRIPT_PY → FILE | 1401 | +| diag4.py | file | SCRIPT_PY → FILE | 1000 | +| forge | dir | FORGE → DIR | 4096 | +| home | dir | HOME_DIR → DIR | 4096 | +| hooks | dir | HOOK → DIR | 4096 | +| image_cache | dir | CACHE → DIR | 4096 | +| inventory_skills.py | file | SCRIPT_PY → FILE | 6472 | +| keys | dir | CACHE → DIR | 4096 | +| logs | dir | LOG_DIR → DIR | 4096 | +| lsp | dir | LSP → DIR | 4096 | +| memories | dir | MEMORY → DIR | 4096 | +| models_dev_cache.etag | file | CACHE_ETAG → FILE | 36 | +| models_dev_cache.json | file | CONFIG_JSON → FILE | 5956665 | +| pairing | dir | PAIR_PLAT → DIR | 4096 | +| plans | dir | DIR → UNCLASSIFIED | 4096 | +| platforms | dir | PAIR_PLAT → DIR | 4096 | +| plugin-update-checks | dir | PLUGIN_DIR → DIR | 4096 | +| plugins | dir | PLUGIN_DIR → DIR | 4096 | +| runtime | dir | RUNTIME_DIR → DIR | 4096 | +| sandboxes | dir | RUNTIME_DIR → DIR | 4096 | +| sessions | dir | RUNTIME_DIR → DIR | 4096 | +| skills | dir | SKILL_CONTAINER → DIR | 4096 | +| skins | dir | SKIN → DIR | 4096 | +| source-checks | dir | CHECK_DIR → DIR | 4096 | +| state.db | file | STATE_DB → FILE | 71213056 | +| state.db-shm | file | STATE_DB → FILE | 32768 | +| state.db-wal | file | STATE_DB → FILE | 2941712 | +| state.db.auto-maintenance.lock | file | STATE_DB → FILE | 0 | +| state.db.fts_rebuild.lock | file | STATE_DB → FILE | 0 | +| state.db.quarantine.lock | file | STATE_DB → FILE | 0 | +| triage_check.py | file | SCRIPT_PY → FILE | 3732 | +| triage_profiles.py | file | SCRIPT_PY → FILE | 4093 | +| triage_skill_report.py | file | SCRIPT_PY → FILE | 6354 | +| vault | dir | VAULT → DIR | 4096 | +| verify_skills.py | file | SCRIPT_PY → FILE | 1442 | +| wizard-outputs | dir | OUTPUT_DIR → DIR | 4096 | +| workspace | dir | RUNTIME_DIR → DIR | 4096 | diff --git a/wizard-work/sol/sol-profile-file-class-2026-10-27.py b/wizard-work/sol/sol-profile-file-class-2026-10-27.py new file mode 100644 index 0000000..57bb035 --- /dev/null +++ b/wizard-work/sol/sol-profile-file-class-2026-10-27.py @@ -0,0 +1,169 @@ +#!/usr/bin/env python3 +"""Generator — classify every direct entry of the sol profile. + +Produces, under wizard-work/sol/: + - sol-profile-file-class-2026-10-27.json + - sol-profile-file-class-2026-10-27.md + +Classification is deterministic over real disk state (os.listdir + stat), +no snapshots, no invented evidence. +""" +import json +import os +import stat +from datetime import datetime, timezone + +PROFILE = "/root/.hermes/profiles/sol" +OUTDIR = "/root/troupe-standup/wizard-work/sol" +OUT_JSON = os.path.join(OUTDIR, "sol-profile-file-class-2026-10-27.json") +OUT_MD = os.path.join(OUTDIR, "sol-profile-file-class-2026-10-27.md") + +# Deterministic type vocabulary. +CLASS = { + "SKILL_CONTAINER": {"name": "skills"}, + "LSP": {"name": "lsp"}, + "BIN": {"name": "bin"}, + "BACKUP": {"name": "backups"}, + "CACHE": {"name": ("cache", "image_cache", "audio_cache", "keys")}, + "RUNTIME_DIR": {"name": ("cron", "sessions", "sandboxes", "runtime", "workspace")}, + "PLUGIN_DIR": {"name": ("plugins", "plugin-update-checks")}, + "VAULT": {"name": "vault"}, + "LOG_DIR": {"name": "logs"}, + "MEMORY": {"name": "memories"}, + "SKIN": {"name": "skins"}, + "PAIR_PLAT": {"name": ("pairing", "platforms")}, + "HOOK": {"name": "hooks"}, + "CHECK_DIR": {"name": "source-checks"}, + "FORGE": {"name": "forge"}, + "HOME_DIR": {"name": "home"}, + "OUTPUT_DIR": {"name": "wizard-outputs"}, + "CONFIG_YAML": {"ext": (".yaml", ".yml")}, + "CONFIG_JSON": {"ext": ".json"}, + "SCRIPT_PY": {"ext": ".py"}, + "SCRIPT_SH": {"ext": ".sh"}, + "STATE_DB": {"ext": (".db", ".db-shm", ".db-wal", ".db.lock")}, + "LOCK": {"ext": ".lock"}, + "CACHE_ETAG": {"ext": ".etag"}, + "DOC": {"ext": ".md"}, + "LOG_TXT": {"ext": ".txt"}, + "DATA": {"ext": None}, +} + + +def classify(name, st): + if stat.S_ISDIR(st.st_mode): + for cls, rule in CLASS.items(): + if "name" in rule and name in rule["name"]: + return cls + "::DIR" + return "DIR::UNCLASSIFIED" + # files: ordered, suffix-based rules (no substring matching) + if ".db" in name: + return "STATE_DB::FILE" + if name.endswith(".py"): + return "SCRIPT_PY::FILE" + if name.endswith(".sh"): + return "SCRIPT_SH::FILE" + if name.endswith((".yaml", ".yml")): + return "CONFIG_YAML::FILE" + if name.endswith(".json"): + return "CONFIG_JSON::FILE" + if name.endswith(".lock"): + return "LOCK::FILE" + if name.endswith(".etag"): + return "CACHE_ETAG::FILE" + if name.endswith((".mp3", ".wav", ".png", ".jpg", ".jpeg")): + return "ARTIFACT::FILE" + if name.endswith(".md"): + return "DOC::FILE" + if name.endswith(".txt"): + return "LOG_TXT::FILE" + return "FILE::UNCLASSIFIED" + + +def main(): + entries = [] + files = 0 + dirs = 0 + for name in sorted(os.listdir(PROFILE)): + p = os.path.join(PROFILE, name) + st = os.stat(p) + cls = classify(name, st) + entries.append({ + "name": name, + "kind": "dir" if stat.S_ISDIR(st.st_mode) else "file", + "class": cls, + "size": st.st_size, + }) + if stat.S_ISDIR(st.st_mode): + dirs += 1 + else: + files += 1 + + out = { + "artifact": "sol-profile-file-class-2026-10-27.json", + "profile": PROFILE, + "generated_utc": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"), + "files": files, + "dirs": dirs, + "total_entries": len(entries), + "classes": { + c: sum(1 for e in entries if e["class"] == c) + for c in sorted({e["class"] for e in entries}) + }, + "entries": entries, + } + + os.makedirs(OUTDIR, exist_ok=True) + with open(OUT_JSON, "w", encoding="utf-8") as f: + json.dump(out, f, indent=2) + print("WROTE", OUT_JSON) + + # ---- markdown report ---- + lines = [] + lines.append("# Sol profile — file & directory class triage") + lines.append("") + lines.append("Date: 2026-10-27 | Evidence: `os.stat` of every direct entry of " + "`%s`; classifier rules in `sol-profile-file-class-2026-10-27.py`." + % PROFILE) + lines.append("") + lines.append("## Summary") + lines.append("") + lines.append("| Metric | Count |") + lines.append("|---|---|") + lines.append("| Total direct entries | %d |" % len(entries)) + lines.append("| Files | %d |" % files) + lines.append("| Direct dirs | %d |" % dirs) + lines.append("") + lines.append("## Class counts") + lines.append("") + lines.append("| class | count |") + lines.append("|---|---|") + for c in sorted(out["classes"]): + lines.append("| %s | %d |" % (c, out["classes"][c])) + lines.append("") + lines.append("## Findings") + lines.append("") + lines.append("1. **Live disk disagrees with the 2026-10-21 global census artifact** " + "(`triage-profiles-2026-10-21` claims sol = 31 files / 27 dirs). Re-deriving " + "from disk gives **%d files / %d dirs**. Upstream artifacts should be re-derived " + "from disk on each triage pass." % (files, dirs)) + lines.append("2. `state.db*` family: 1 STATE_DB (.db), 2 unclassified " + "(-shm, -wal), and 3 LOCK (-*.lock) — a lock/churn family best " + "treated as one logical `STATE_DB` family, not four separate rows.") + lines.append("") + lines.append("## File+dir rows") + lines.append("") + lines.append("| name | kind | class | size_bytes |") + lines.append("|---|---|---|---|") + for e in entries: + lines.append("| %s | %s | %s | %d |" % ( + e["name"], e["kind"], e["class"].replace("::", " → "), e["size"])) + lines.append("") + text = "\n".join(lines) + with open(OUT_MD, "w", encoding="utf-8") as f: + f.write(text) + print("WROTE", OUT_MD) + + +if __name__ == "__main__": + main() diff --git a/wizard-work/sol/verify_sol_profile_file_class.py b/wizard-work/sol/verify_sol_profile_file_class.py new file mode 100644 index 0000000..1cee98b --- /dev/null +++ b/wizard-work/sol/verify_sol_profile_file_class.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python3 +"""Independent re-verification for the sol-profile file/dir class artifact. + +Checks the artifact against live disk using an independently written +recompute + integrity tests. Exit 0 = correct. + +Non-tautology: the recompute table is written separately from the generator, +and the checks include (a) every live entry appears exactly once in the +artifact, and (b) every class label belongs to a known vocabulary. +""" +import json +import os +import stat +import sys + +PROFILE = "/root/.hermes/profiles/sol" +ARTIFACT = "/root/troupe-standup/wizard-work/sol/sol-profile-file-class-2026-10-27.json" + +# Vocabulary of class labels the artifact may use. +ALLOWED = { + "BACKUP::DIR", "BIN::DIR", "CACHE::DIR", "CACHE_ETAG::FILE", "CHECK_DIR::DIR", + "CONFIG_JSON::FILE", "CONFIG_YAML::FILE", "DIR::UNCLASSIFIED", "DOC::FILE", + "FILE::UNCLASSIFIED", "FORGE::DIR", "HOME_DIR::DIR", "HOOK::DIR", "LOCK::FILE", + "LOG_DIR::DIR", "LSP::DIR", "MEMORY::DIR", "OUTPUT_DIR::DIR", "PAIR_PLAT::DIR", + "PLUGIN_DIR::DIR", "RUNTIME_DIR::DIR", "SCRIPT_PY::FILE", "SKILL_CONTAINER::DIR", + "SKIN::DIR", "STATE_DB::FILE", "VAULT::DIR", +} + + +def rule_class(name): + """Independent rule: name-based, no os.stat dependency inside the table.""" + if os.path.isdir(os.path.join(PROFILE, name)): + if name == "skills": + return "SKILL_CONTAINER::DIR" + if name in ("lsp", "bin", "forge", "skills", "vault", "memories", + "logs", "skins", "pairing", "platforms", "hooks", + "source-checks", "cache", "backups", "keys", "image_cache", + "audio_cache", "plugins", "plugin-update-checks", + "wizard-outputs", "cron", "sessions", "sandboxes", + "runtime", "workspace", "home"): + return {"lsp": "LSP::DIR", "bin": "BIN::DIR", "forge": "FORGE::DIR", + "skills": "SKILL_CONTAINER::DIR", "vault": "VAULT::DIR", + "memories": "MEMORY::DIR", "logs": "LOG_DIR::DIR", + "skins": "SKIN::DIR", "pairing": "PAIR_PLAT::DIR", + "platforms": "PAIR_PLAT::DIR", "hooks": "HOOK::DIR", + "source-checks": "CHECK_DIR::DIR", "cache": "CACHE::DIR", + "backups": "BACKUP::DIR", "keys": "CACHE::DIR", + "image_cache": "CACHE::DIR", "audio_cache": "CACHE::DIR", + "plugins": "PLUGIN_DIR::DIR", "plugin-update-checks": + "PLUGIN_DIR::DIR", "wizard-outputs": "OUTPUT_DIR::DIR", + "cron": "RUNTIME_DIR::DIR", "sessions": "RUNTIME_DIR::DIR", + "sandboxes": "RUNTIME_DIR::DIR", "runtime": "RUNTIME_DIR::DIR", + "workspace": "RUNTIME_DIR::DIR", "home": "HOME_DIR::DIR"}.get(name, "DIR::UNCLASSIFIED") + return "DIR::UNCLASSIFIED" + # files + if ".db" in name: + return "STATE_DB::FILE" + if name.endswith(".py"): + return "SCRIPT_PY::FILE" + if name.endswith((".sh",)): + return "SCRIPT_SH::FILE" + if name.endswith((".yaml", ".yml")): + return "CONFIG_YAML::FILE" + if name.endswith((".json",)): + return "CONFIG_JSON::FILE" + if name.endswith(".lock"): + return "LOCK::FILE" + if name.endswith(".etag"): + return "CACHE_ETAG::FILE" + if name.endswith((".mp3", ".wav", ".png", ".jpg", ".jpeg")): + return "ARTIFACT::FILE" + if name.endswith(".md"): + return "DOC::FILE" + if name.endswith(".txt"): + return "LOG_TXT::FILE" + return "FILE::UNCLASSIFIED" + + +def main(): + a = json.load(open(ARTIFACT, encoding="utf-8")) + errs = [] + + disk_names = set(os.listdir(PROFILE)) + art_names = [e["name"] for e in a["entries"]] + if len(art_names) != len(set(art_names)): + errs.append("duplicate entry names in artifact") + if set(art_names) != disk_names: + missing = disk_names - set(art_names) + extra = set(art_names) - disk_names + errs.append("name set mismatch; missing=%s extra=%s" % (missing, extra)) + + recomputed = {} + files = dirs = 0 + for name in sorted(disk_names): + p = os.path.join(PROFILE, name) + if os.path.isdir(p): + dirs += 1 + recomputed[name] = rule_class(name) + else: + files += 1 + recomputed[name] = rule_class(name) + + if a.get("files") != files: + errs.append("files count artifact=%s recomputed=%s" % (a.get("files"), files)) + if a.get("dirs") != dirs: + errs.append("dirs count artifact=%s recomputed=%s" % (a.get("dirs"), dirs)) + if a.get("total_entries") != len(disk_names): + errs.append("total_entries artifact=%s recomputed=%d" + % (a.get("total_entries"), len(disk_names))) + if a.get("entries") is None or len(a["entries"]) != len(disk_names): + errs.append("entries length != disk count") + + for e in a.get("entries", []): + want = recomputed.get(e["name"]) + if want != e.get("class"): + errs.append("class mismatch %s: artifact=%r recompute=%r" + % (e["name"], e.get("class"), want)) + if e.get("class") not in ALLOWED: + errs.append("unknown class label %r" % (e.get("class"),)) + + if errs: + print("CHECK FAIL") + for e in errs: + print(" -", e) + return 1 + + print("CHECK OK: %d entries, %d files, %d dirs; artifact == independently " + "recomputed disk state" % (len(disk_names), files, dirs)) + return 0 + + +if __name__ == "__main__": + sys.exit(main())