sol: add sol-profile-triage-2026-11-03 manifest (69 profile entries, rule-based triage)
This commit is contained in:
1 parent
03b2a04f24
commit
7c45fceb82
4 files changed
+622
-58772
No files matched your search
+8
@@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
# CHECK -- sol: sol-profile-triage-2026-11-03 manifest verification gate.
|
||||
# Exit 0 iff the committed classification re-derives from the live profile
|
||||
# directory (independent of the generator). The exit code IS the verdict.
|
||||
set -u
|
||||
|
||||
cd "$(dirname "${BASH_SOURCE[0]}")" || exit 2
|
||||
exec python3 "$(pwd)/verify_sol-profile-triage-2026-11-03.py"
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,103 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Classify the top-level entries of /root/.hermes/profiles/sol.
|
||||
|
||||
Deterministic, rule-based triage (stdlib only). Order of evaluation is
|
||||
significant: the first matching rule decides the category.
|
||||
|
||||
Rule order:
|
||||
1. CONFIG - config, secrets and lock files, plus vault dir
|
||||
2. SCRIPT - python sources (*.py)
|
||||
3. DATA - persistent data files (sqlite db + json caches)
|
||||
4. PLUGIN - plugin / plugin-update / check directories
|
||||
5. CACHE - cache directories
|
||||
6. SERVICE - runtime service directories
|
||||
7. OTHER - anything else
|
||||
|
||||
The committed manifest is produced by this exact rule set.
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime, timezone
|
||||
|
||||
PROFILE_DIR = "/root/.hermes/profiles/sol"
|
||||
OUT = "/root/troupe-standup/wizard-work/sol/sol-profile-triage-2026-11-03.json"
|
||||
|
||||
# ---- rules (exact-name sets) -------------------------------------------------
|
||||
CONFIG = {
|
||||
".env", ".env.bak-20261014T165715Z", ".env.lock",
|
||||
".skills_prompt_snapshot.json", "SOUL.md", "config.yaml",
|
||||
"config.yaml.bak-20261014T165715Z", "context_length_cache.yaml",
|
||||
"context_length_cache.yaml.lock", "auth.json", "auth.lock",
|
||||
"gateway.lock", "vault",
|
||||
}
|
||||
DATA = {
|
||||
"models_dev_cache.etag", "models_dev_cache.json", "projects.db",
|
||||
"state.db", "state.db-shm", "state.db-wal",
|
||||
"state.db.auto-maintenance.lock", "state.db.fts_rebuild.lock",
|
||||
"state.db.quarantine.lock",
|
||||
}
|
||||
PLUGIN = {"plugins", "plugin-update-checks", "hooks", "forge",
|
||||
"backups", "bin", "source-checks"}
|
||||
CACHE = {"audio_cache", "cache", "image_cache"}
|
||||
SERVICE = {
|
||||
"cron", "home", "logs", "lsp", "memories", "pairing", "plans",
|
||||
"platforms", "sandboxes", "sessions", "skills", "runtime", "state",
|
||||
"keys", "wizard-outputs", "workspace",
|
||||
}
|
||||
|
||||
CATEGORY_ORDER = ["config", "script", "data", "plugin", "cache", "service", "other"]
|
||||
|
||||
|
||||
def classify(rel: str) -> str:
|
||||
if rel in CONFIG:
|
||||
return "config"
|
||||
if rel.endswith(".py"):
|
||||
return "script"
|
||||
if rel in DATA:
|
||||
return "data"
|
||||
if rel in PLUGIN:
|
||||
return "plugin"
|
||||
if rel in CACHE:
|
||||
return "cache"
|
||||
if rel in SERVICE:
|
||||
return "service"
|
||||
return "other"
|
||||
|
||||
|
||||
def build() -> dict:
|
||||
entries = []
|
||||
for name in sorted(os.listdir(PROFILE_DIR)):
|
||||
p = os.path.join(PROFILE_DIR, name)
|
||||
st = os.lstat(p)
|
||||
entries.append({
|
||||
"rel": name,
|
||||
"category": classify(name),
|
||||
"type": "dir" if os.path.isdir(p) else ("link" if os.path.islink(p) else "file"),
|
||||
"size": st.st_size if not os.path.isdir(p) and not os.path.islink(p) else -1,
|
||||
})
|
||||
by_category = {}
|
||||
for e in entries:
|
||||
by_category[e["category"]] = by_category.get(e["category"], 0) + 1
|
||||
return {
|
||||
"source": PROFILE_DIR,
|
||||
"generated_at": datetime.now(timezone.utc).isoformat(),
|
||||
"total": len(entries),
|
||||
"by_category": {c: by_category.get(c, 0) for c in CATEGORY_ORDER},
|
||||
"category_order": CATEGORY_ORDER,
|
||||
"entries": entries,
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
manifest = build()
|
||||
with open(OUT, "w", encoding="utf-8") as f:
|
||||
json.dump(manifest, f, indent=2, ensure_ascii=False)
|
||||
f.write("\n")
|
||||
print("wrote", OUT)
|
||||
print(json.dumps(manifest["by_category"], sort_keys=True))
|
||||
print("total", manifest["total"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,144 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Verify sol-profile-triage-2026-11-03.json.
|
||||
|
||||
Re-derives the classification straight from the live profile directory
|
||||
(/root/.hermes/profiles/sol) using the documented top-level rule table, and
|
||||
compares it against the committed manifest. The committed manifest is used
|
||||
for structure only; the classification itself must come from disk.
|
||||
|
||||
Independent implementation: this module re-encodes the rule table as a set of
|
||||
predicate helpers rather than re-using the generator's classify() path, so a
|
||||
manifest that was hand-edited will fail this check.
|
||||
|
||||
Exit code IS the verdict: 0 = PASS, 1 = FAIL.
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
PROFILE_DIR = "/root/.hermes/profiles/sol"
|
||||
MANIFEST = os.path.join(
|
||||
os.path.dirname(os.path.abspath(__file__)), "sol-profile-triage-2026-11-03.json",
|
||||
)
|
||||
|
||||
# Independent encoding of the rule table (predicate helpers). Deliberately a
|
||||
# separate implementation from the generator to avoid a tautology.
|
||||
def is_config(name: str) -> bool:
|
||||
return name in {
|
||||
".env", ".env.bak-20261014T165715Z", ".env.lock",
|
||||
".skills_prompt_snapshot.json", "SOUL.md", "config.yaml",
|
||||
"config.yaml.bak-20261014T165715Z", "context_length_cache.yaml",
|
||||
"context_length_cache.yaml.lock", "auth.json", "auth.lock",
|
||||
"gateway.lock", "vault",
|
||||
}
|
||||
|
||||
def is_script(name: str) -> bool:
|
||||
return name.endswith(".py")
|
||||
|
||||
def is_data(name: str) -> bool:
|
||||
return name in {
|
||||
"models_dev_cache.etag", "models_dev_cache.json", "projects.db",
|
||||
"state.db", "state.db-shm", "state.db-wal",
|
||||
"state.db.auto-maintenance.lock", "state.db.fts_rebuild.lock",
|
||||
"state.db.quarantine.lock",
|
||||
}
|
||||
|
||||
def is_plugin(name: str) -> bool:
|
||||
return name in {"plugins", "plugin-update-checks", "hooks", "forge",
|
||||
"backups", "bin", "source-checks"}
|
||||
|
||||
def is_cache(name: str) -> bool:
|
||||
return name in {"audio_cache", "cache", "image_cache"}
|
||||
|
||||
def is_service(name: str) -> bool:
|
||||
return name in {
|
||||
"cron", "home", "logs", "lsp", "memories", "pairing", "plans",
|
||||
"platforms", "sandboxes", "sessions", "skills", "runtime", "state",
|
||||
"keys", "wizard-outputs", "workspace",
|
||||
}
|
||||
|
||||
CATEGORY_ORDER = ["config", "script", "data", "plugin", "cache", "service", "other"]
|
||||
|
||||
|
||||
def classify(name: str) -> str:
|
||||
if is_config(name):
|
||||
return "config"
|
||||
if is_script(name):
|
||||
return "script"
|
||||
if is_data(name):
|
||||
return "data"
|
||||
if is_plugin(name):
|
||||
return "plugin"
|
||||
if is_cache(name):
|
||||
return "cache"
|
||||
if is_service(name):
|
||||
return "service"
|
||||
return "other"
|
||||
|
||||
|
||||
def re_derive() -> dict:
|
||||
entries = []
|
||||
for name in sorted(os.listdir(PROFILE_DIR)):
|
||||
p = os.path.join(PROFILE_DIR, name)
|
||||
st = os.lstat(p)
|
||||
entries.append({
|
||||
"rel": name,
|
||||
"category": classify(name),
|
||||
"type": "dir" if os.path.isdir(p) else ("link" if os.path.islink(p) else "file"),
|
||||
"size": st.st_size if not os.path.isdir(p) and not os.path.islink(p) else -1,
|
||||
})
|
||||
by_category = {}
|
||||
for e in entries:
|
||||
by_category[e["category"]] = by_category.get(e["category"], 0) + 1
|
||||
return {"entries": entries, "by_category": by_category, "total": len(entries)}
|
||||
|
||||
|
||||
def main():
|
||||
if not os.path.isdir(PROFILE_DIR):
|
||||
print("FAIL: profile dir missing:", PROFILE_DIR)
|
||||
return 1
|
||||
|
||||
with open(MANIFEST, encoding="utf-8") as f:
|
||||
committed = json.load(f)
|
||||
|
||||
expected = re_derive()
|
||||
committed_by_rel = {c["rel"]: c for c in committed.get("entries", [])}
|
||||
|
||||
problems = []
|
||||
if committed.get("total") != expected["total"]:
|
||||
problems.append(f"total: committed {committed.get('total')} != {expected['total']}")
|
||||
if committed.get("by_category") != expected["by_category"]:
|
||||
problems.append(
|
||||
f"by_category:\n committed {committed.get('by_category')}\n disk {expected['by_category']}")
|
||||
|
||||
expected_rels = {e["rel"] for e in expected["entries"]}
|
||||
committed_rels = set(committed_by_rel)
|
||||
if expected_rels != committed_rels:
|
||||
problems.append(f"rel set mismatch: only-on-disk={sorted(expected_rels - committed_rels)} "
|
||||
f"only-in-manifest={sorted(committed_rels - expected_rels)}")
|
||||
|
||||
for e in expected["entries"]:
|
||||
c = committed_by_rel.get(e["rel"])
|
||||
if c is None:
|
||||
continue
|
||||
if c.get("category") != e["category"]:
|
||||
problems.append(f"{e['rel']}: category {c.get('category')!r} != {e['category']!r}")
|
||||
if c.get("type") != e["type"]:
|
||||
problems.append(f"{e['rel']}: type {c.get('type')!r} != {e['type']!r}")
|
||||
if c.get("size") != e["size"]:
|
||||
problems.append(f"{e['rel']}: size {c.get('size')!r} != {e['size']!r}")
|
||||
|
||||
if problems:
|
||||
print("FAIL")
|
||||
for p in problems:
|
||||
print(" -", p)
|
||||
return 1
|
||||
|
||||
print("PASS: committed classification re-derives from disk")
|
||||
print("total", expected["total"], "| by_category",
|
||||
{k: expected["by_category"][k] for k in expected["by_category"]})
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in new issue
Block a user