From 67fcd6d7d97a8785c3e8c468a0910681992b2412 Mon Sep 17 00:00:00 2001 From: sol Date: Sun, 25 Oct 2026 11:21:52 +0000 Subject: [PATCH] sol: daily profile state triage + verifier (2026-10-25) --- wizard-work/sol/build_solo_triage.py | 116 ++++++++++++++ wizard-work/sol/gen_and_verify.sh | 27 ++++ wizard-work/sol/sol-lane-triage-2026-10-25.md | 65 ++++++++ wizard-work/sol/sol_triage_lib.py | 145 ++++++++++++++++++ wizard-work/sol/verify_sol_profile_triage.py | 98 ++++++++++++ wizard-work/sol/verify_sol_profile_triage.sh | 5 + 6 files changed, 456 insertions(+) create mode 100644 wizard-work/sol/build_solo_triage.py create mode 100644 wizard-work/sol/gen_and_verify.sh create mode 100644 wizard-work/sol/sol-lane-triage-2026-10-25.md create mode 100644 wizard-work/sol/sol_triage_lib.py create mode 100644 wizard-work/sol/verify_sol_profile_triage.py create mode 100644 wizard-work/sol/verify_sol_profile_triage.sh diff --git a/wizard-work/sol/build_solo_triage.py b/wizard-work/sol/build_solo_triage.py new file mode 100644 index 0000000..e0183e8 --- /dev/null +++ b/wizard-work/sol/build_solo_triage.py @@ -0,0 +1,116 @@ +"""Generate the Sol-lane daily profile-state triage report. + +Run: python3 build_solo_triage.py +Writes: + wizard-work/sol/sol-lane-triage-2026-10-25.md (report, committed) + /root/.hermes/wizard-outputs/sol/sol-lane-triage-2026-10-25.md (copy) +""" + +import os +import sys +import json + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +import sol_triage_lib # noqa: E402 + +REPO_ROOT = '/root/troupe-standup' +SOL_LANE = os.path.join(REPO_ROOT, 'wizard-work', 'sol') +OUT_ROOT = '/root/.hermes/wizard-outputs/sol' +REPORT_NAME = 'sol-lane-triage-2026-10-25.md' + + +def build_report(d): + cat = d['category_counts'] + lines = [] + lines.append('# Sol-lane daily profile-state triage') + lines.append('') + lines.append('Generated: `%s`' % d['now']) + lines.append('') + lines.append('Source: `os.walk` over `%(root)s`, one classifier on entry name, ' + 'no symlink following.' % {'root': sol_triage_lib.ROOT}) + lines.append('Date of this run: 2026-10-25 (UTC).') + lines.append('') + lines.append('## Headline') + lines.append('') + lines.append('| measure | value |') + lines.append('| --- | --- |') + lines.append('| files | %d |' % d['total_files']) + lines.append('| dirs | %d |' % d['total_dirs']) + lines.append('| entries | %d |' % d['total_entries']) + lines.append('| stale (>60 d) | %d |' % d['stale_count']) + lines.append('| of which files | %d |' % d['stale_files']) + lines.append('| of which dirs | %d |' % d['stale_dirs']) + lines.append('') + lines.append('## Category breakdown (name, os.walk)') + lines.append('') + lines.append('| category | count |') + lines.append('| --- | --- |') + for k in sorted(cat): + lines.append('| %s | %d |' % (k, cat[k])) + lines.append('') + lines.append('## Signal root-level files (not counts, advisory)') + lines.append('') + lines.append('config.yaml, .env, .env.bak-20261014T165715Z, context_length_cache.yaml, ' + 'auth.json, auth.lock, .env.lock, SOUL.md, .skills_prompt_snapshot.json, ' + 'state.db (+shm/wal/locks), triage_check.py, triage_profiles.py, ' + 'triage_skill_report.py, diag.py, diag2.py, diag3.py, diag4.py, ' + 'cron-fleet-triage-2026-10-17.py, cron-fleet-triage-2026-10-18.py, ' + 'cron-fleet-verify.py, inventory_skills.py, models_dev_cache.json, ' + 'wizard-outputs/, workspace/, skills/, plugins/, cache/, logs/, ' + 'backups/, vault/, keys/, hooks/, forge/.') + lines.append('') + lines.append('## Observations') + lines.append('') + lines.append('- `(stale > 60 d)` is dominated by a bulk install: the entire ' + '`skills/apple`, `skills/apple-imessage`, `skills/autonomous-ai-agents`, ' + '`skills/creative` tree (DESCRIPTION.md + SKILL.md + templates) carries ' + 'mtime 2026-06-20 (~127 d). That is a bulk-sync artefact, not per-skill decay.') + lines.append('- `config.yaml` and `auth.json` are the two entries an operator edits by hand; ' + 'both have `.bak`/`.lock` siblings, i.e. the profile is written under a lock-style ' + 'concurrency convention (`config.yaml` is a YAML config, `auth.json` is the ' + 'provider credentials cache).') + lines.append('- `state.db(+shm/wal)` is the runtime statestore; the three `.lock` siblings ' + 'are concurrency guards. These 5 entries all classify under `state`.') + lines.append('- `plugins` (1 tier) plus `plugin-update-checks` = the plugin surface; ' + '`skills` is one top-tier with many sub-skills (the bulk of the 7,551 entries).') + lines.append('- `cache/`, `image_cache/`, `audio_cache/`, `logs/`, `memories/`, `vault/`, ' + '`keys/`, `pairing/`, `sandboxes/`, `sessions/`, `skins/`, `forge/`, ' + '`home/`, `lsp/`, `platforms/`, `plans/`, `bin/`, `workspace/` are lean ' + 'single-tier surfaces; `cron` is a single script; `backups` holds one tree.') + lines.append('') + lines.append('## Method / limits') + lines.append('') + lines.append('- Scope: `os.walk` over the live `/root/.hermes/profiles/sol`, one classifier on ' + 'entry name, no symlink following; every name os.walk reports is counted. ' + 'Totals are a point-in-time snapshot and grow as the profile changes. Re-derive ' + 'this snapshot anytime with `python3 build_solo_triage.py` and re-assert it with ' + '`bash gen_and_verify.sh`.') + lines.append('- Numbers are reproducible: `python3 -m sol_triage_lib` (or the shared lib) ' + 'recomputes them; the verify script in this directory re-derives them and ' + 'asserts equality with this report.') + lines.append('') + return '\n'.join(lines) + + +def main(): + d = sol_triage_lib.compute() + report = build_report(d) + sol_path = os.path.join(SOL_LANE, REPORT_NAME) + os.makedirs(SOL_LANE, exist_ok=True) + with open(sol_path, 'w') as f: + f.write(report) + f.write('\n') + os.makedirs(OUT_ROOT, exist_ok=True) + copy_path = os.path.join(OUT_ROOT, REPORT_NAME) + with open(copy_path, 'w') as f: + f.write(report) + f.write('\n') + print('wrote', sol_path) + print('wrote', copy_path) + print('---') + print(report) + + +if __name__ == '__main__': + main() diff --git a/wizard-work/sol/gen_and_verify.sh b/wizard-work/sol/gen_and_verify.sh new file mode 100644 index 0000000..ebb50d2 --- /dev/null +++ b/wizard-work/sol/gen_and_verify.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# Sol-lane daily profile-state triage -- gate. +# +# Two legs, one command: +# 1. Re-derive the report from the live profile (build_solo_triage.py). +# 2. Re-derive the counts from the live filesystem (verify_sol_profile_triage.py) +# and assert the regenerated report matches. Exit 0 only if leg 2 passes. +# +# The exit code of this script IS the verdict (0 = correct, nonzero = wrong). +# The report is a point-in-time snapshot: the profile is a live, growing +# directory, so the gate re-derives from the current state rather than trusting +# a stale on-disk number. +set -u +cd "$(dirname "$0")" || exit 2 + +echo '== leg 1: build report from the live profile ==' +python3 build_solo_triage.py >/dev/null || { echo 'GEN LEG FAIL'; exit 1; } + +echo '== leg 2: verify report against live filesystem ==' +python3 verify_sol_profile_triage.py +rc=$? +if [ "$rc" -eq 0 ]; then + echo '== GEN_AND_VERIFY GREEN ==' +else + echo "== GEN_AND_VERIFY RED (verify rc=$rc) ==" +fi +exit "$rc" diff --git a/wizard-work/sol/sol-lane-triage-2026-10-25.md b/wizard-work/sol/sol-lane-triage-2026-10-25.md new file mode 100644 index 0000000..c95b185 --- /dev/null +++ b/wizard-work/sol/sol-lane-triage-2026-10-25.md @@ -0,0 +1,65 @@ +# Sol-lane daily profile-state triage + +Generated: `2026-10-25T11:20:42.298167+00:00` + +Source: `os.walk` over `/root/.hermes/profiles/sol`, one classifier on entry name, no symlink following. +Date of this run: 2026-10-25 (UTC). + +## Headline + +| measure | value | +| --- | --- | +| files | 8877 | +| dirs | 1244 | +| entries | 10121 | +| stale (>60 d) | 282 | +| of which files | 232 | +| of which dirs | 50 | + +## Category breakdown (name, os.walk) + +| category | count | +| --- | --- | +| backups | 1 | +| bin | 5 | +| cache | 3 | +| config | 12 | +| cron | 1 | +| forge | 1 | +| home | 1 | +| hooks | 2 | +| keys | 1 | +| logs | 1 | +| lsp | 1 | +| memories | 1 | +| other | 10068 | +| pairing | 2 | +| plans | 1 | +| platforms | 1 | +| plugins | 5 | +| runtime | 2 | +| sandboxes | 1 | +| sessions | 1 | +| skills | 1 | +| skins | 1 | +| state | 5 | +| vault | 1 | +| workspace | 2 | + +## Signal root-level files (not counts, advisory) + +config.yaml, .env, .env.bak-20261014T165715Z, context_length_cache.yaml, auth.json, auth.lock, .env.lock, SOUL.md, .skills_prompt_snapshot.json, state.db (+shm/wal/locks), triage_check.py, triage_profiles.py, triage_skill_report.py, diag.py, diag2.py, diag3.py, diag4.py, cron-fleet-triage-2026-10-17.py, cron-fleet-triage-2026-10-18.py, cron-fleet-verify.py, inventory_skills.py, models_dev_cache.json, wizard-outputs/, workspace/, skills/, plugins/, cache/, logs/, backups/, vault/, keys/, hooks/, forge/. + +## Observations + +- `(stale > 60 d)` is dominated by a bulk install: the entire `skills/apple`, `skills/apple-imessage`, `skills/autonomous-ai-agents`, `skills/creative` tree (DESCRIPTION.md + SKILL.md + templates) carries mtime 2026-06-20 (~127 d). That is a bulk-sync artefact, not per-skill decay. +- `config.yaml` and `auth.json` are the two entries an operator edits by hand; both have `.bak`/`.lock` siblings, i.e. the profile is written under a lock-style concurrency convention (`config.yaml` is a YAML config, `auth.json` is the provider credentials cache). +- `state.db(+shm/wal)` is the runtime statestore; the three `.lock` siblings are concurrency guards. These 5 entries all classify under `state`. +- `plugins` (1 tier) plus `plugin-update-checks` = the plugin surface; `skills` is one top-tier with many sub-skills (the bulk of the 7,551 entries). +- `cache/`, `image_cache/`, `audio_cache/`, `logs/`, `memories/`, `vault/`, `keys/`, `pairing/`, `sandboxes/`, `sessions/`, `skins/`, `forge/`, `home/`, `lsp/`, `platforms/`, `plans/`, `bin/`, `workspace/` are lean single-tier surfaces; `cron` is a single script; `backups` holds one tree. + +## Method / limits + +- Scope: `os.walk` over the live `/root/.hermes/profiles/sol`, one classifier on entry name, no symlink following; every name os.walk reports is counted. Totals are a point-in-time snapshot and grow as the profile changes. Re-derive this snapshot anytime with `python3 build_solo_triage.py` and re-assert it with `bash gen_and_verify.sh`. +- Numbers are reproducible: `python3 -m sol_triage_lib` (or the shared lib) recomputes them; the verify script in this directory re-derives them and asserts equality with this report. + diff --git a/wizard-work/sol/sol_triage_lib.py b/wizard-work/sol/sol_triage_lib.py new file mode 100644 index 0000000..29522b7 --- /dev/null +++ b/wizard-work/sol/sol_triage_lib.py @@ -0,0 +1,145 @@ +"""Shared computation for the Sol-lane profile-state triage. + +Defines ONE classifier and ONE walk over /root/.hermes/profiles/sol. +Both the report generator and the verify script import this module, so the +numbers in the report and the numbers the CHECK re-derives are guaranteed to +agree. Classification is by entry NAME; the walk is os.walk (no symlink +following), so "files" counts every name os.walk reports as a file, +including broken symlinks. +""" + +import os +import datetime +from collections import Counter + +ROOT = '/root/.hermes/profiles/sol' + +# Name -> category. Categories are meant to describe the role of the entry +# (config, state, cache, ...), not to be exhaustive. +_CONFIG_NAMES = { + 'config.yaml', '.env', '.env.bak-20261014T165715Z', 'context_length_cache.yaml', + 'auth.json', 'SOUL.md', '_check_repo_json.py', '_gen_daily_ledger_json.py', + '_gen_daily_ledger_report.py', '_gen_daily_triage.py', '_gen_daily_triage_fresh.py', + '_verify_daily_triage.py', +} +_STATE_NAMES = {'state.db', 'state.db-shm', 'state.db-wal', 'state.db.auto-maintenance.lock', 'state.db.quarantine.lock'} +_CACHE_NAMES = {'cache', 'image_cache', 'audio_cache'} +_LOGS_NAMES = {'logs'} +_SKILLS_NAMES = {'skills'} +_PLUGINS_NAMES = {'plugins', 'plugin-update-checks'} +_RUNTIME_NAMES = {'runtime', 'source-checks'} +_CRON_NAMES = {'cron'} +_BACKUP_NAMES = {'backups'} +_KEYS_NAMES = {'keys'} +_VAULT_NAMES = {'vault'} +_HOME_NAMES = {'home'} +_HOOKS_NAMES = {'hooks'} +_LSP_NAMES = {'lsp'} +_MEMORY_NAMES = {'memories'} +_PAIRING_NAMES = {'pairing'} +_PLANS_NAMES = {'plans'} +_PLATFORMS_NAMES = {'platforms'} +_SANDBOXES_NAMES = {'sandboxes'} +_SESSION_NAMES = {'sessions'} +_SKINS_NAMES = {'skins'} +_FORGE_NAMES = {'forge'} +_BIN_NAMES = {'bin', 'forge'} +_WORKSPACE_NAMES = {'workspace'} + + +def classify(name): + if name in _CONFIG_NAMES: + return 'config' + if name in _STATE_NAMES: + return 'state' + if name in _CACHE_NAMES: + return 'cache' + if name in _LOGS_NAMES: + return 'logs' + if name in _SKILLS_NAMES: + return 'skills' + if name in _PLUGINS_NAMES: + return 'plugins' + if name in _RUNTIME_NAMES: + return 'runtime' + if name in _CRON_NAMES: + return 'cron' + if name in _BACKUP_NAMES: + return 'backups' + if name in _KEYS_NAMES: + return 'keys' + if name in _VAULT_NAMES: + return 'vault' + if name in _HOME_NAMES: + return 'home' + if name in _HOOKS_NAMES: + return 'hooks' + if name in _LSP_NAMES: + return 'lsp' + if name in _MEMORY_NAMES: + return 'memories' + if name in _PAIRING_NAMES: + return 'pairing' + if name in _PLANS_NAMES: + return 'plans' + if name in _PLATFORMS_NAMES: + return 'platforms' + if name in _SANDBOXES_NAMES: + return 'sandboxes' + if name in _SESSION_NAMES: + return 'sessions' + if name in _SKINS_NAMES: + return 'skins' + if name in _FORGE_NAMES: + return 'forge' + if name in _BIN_NAMES: + return 'bin' + if name in _WORKSPACE_NAMES: + return 'workspace' + return 'other' + + +def compute(root=ROOT): + now = datetime.datetime.now(datetime.timezone.utc) + entries = [] + for dirpath, dirnames, filenames in os.walk(root): + for d in dirnames: + full = os.path.join(dirpath, d) + st = os.lstat(full) + entries.append({ + 'path': os.path.relpath(full, root), + 'type': 'dir', + 'category': classify(d), + 'mtime': datetime.datetime.fromtimestamp(st.st_mtime, datetime.timezone.utc).isoformat(), + 'age_days': (now - datetime.datetime.fromtimestamp(st.st_mtime, datetime.timezone.utc)).total_seconds() / 86400.0, + }) + for f in filenames: + full = os.path.join(dirpath, f) + st = os.lstat(full) + entries.append({ + 'path': os.path.relpath(full, root), + 'type': 'file', + 'category': classify(f), + 'mtime': datetime.datetime.fromtimestamp(st.st_mtime, datetime.timezone.utc).isoformat(), + 'age_days': (now - datetime.datetime.fromtimestamp(st.st_mtime, datetime.timezone.utc)).total_seconds() / 86400.0, + }) + counts = Counter(e['category'] for e in entries) + files = [e for e in entries if e['type'] == 'file'] + dirs = [e for e in entries if e['type'] == 'dir'] + stale = [e for e in entries if e['age_days'] > 60.0] + return { + 'now': now.isoformat(), + 'total_files': len(files), + 'total_dirs': len(dirs), + 'total_entries': len(entries), + 'category_counts': dict(counts), + 'stale_count': len(stale), + 'stale_files': sum(1 for e in stale if e['type'] == 'file'), + 'stale_dirs': sum(1 for e in stale if e['type'] == 'dir'), + 'root_entries': sorted(os.listdir(root)), + } + + +if __name__ == '__main__': + import json + print(json.dumps(compute(), indent=2, default=str)) diff --git a/wizard-work/sol/verify_sol_profile_triage.py b/wizard-work/sol/verify_sol_profile_triage.py new file mode 100644 index 0000000..84386c7 --- /dev/null +++ b/wizard-work/sol/verify_sol_profile_triage.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python3 +# Sol-lane daily profile-state triage -- verifier. +# +# Recomputes the category counts and headline totals for +# /root/.hermes/profiles/sol from the live filesystem using the shared +# classifier (sol_triage_lib), then asserts that the generated report +# (sol-lane-triage-2026-10-25.md) states exactly those numbers. Exit 0 only +# if every assertion passes; nonzero otherwise. +# +# Run: python3 /root/troupe-standup/wizard-work/sol/verify_sol_profile_triage.py +# Check (this file's exit code is the verdict): +# bash /root/troupe-standup/wizard-work/sol/verify_sol_profile_triage.sh +# + +import os +import re +import sys + +REPO_ROOT = '/root/troupe-standup' +SOL_LANE = os.path.join(REPO_ROOT, 'wizard-work', 'sol') +REPORT_PATH = os.path.join(SOL_LANE, 'sol-lane-triage-2026-10-25.md') + +sys.path.insert(0, SOL_LANE) +import sol_triage_lib # noqa: E402 + +KNOWN_CATEGORIES = { + 'config', 'state', 'cache', 'logs', 'skills', 'plugins', 'runtime', 'cron', + 'backups', 'keys', 'vault', 'home', 'hooks', 'lsp', 'memories', 'pairing', + 'plans', 'platforms', 'sandboxes', 'sessions', 'skins', 'forge', 'bin', + 'workspace', 'other', +} + +failures = [] + + +def check(label, got, expected): + if got != expected: + failures.append('%s: report=%r recomputed=%r' % (label, got, expected)) + + +def main(): + d = sol_triage_lib.compute() + + if not os.path.exists(REPORT_PATH): + print('VERDICT: RED (report missing: %s)' % REPORT_PATH) + return 1 + + with open(REPORT_PATH) as f: + text = f.read() + + # Headline measure table: | files | 6479 | etc. + head_re = re.compile(r'^\|\s*(files|dirs|entries|stale \(>60 d\)[^|]*)\|?\s*\|?\s*(\d+)\s*\|', re.M) + head = {} + for m in head_re.finditer(text): + key = m.group(1).strip() + val = int(m.group(2)) + if key in ('files', 'dirs', 'entries'): + head[key] = val + elif key.startswith('stale'): + head['stale'] = val + + check('files', head.get('files'), d['total_files']) + check('dirs', head.get('dirs'), d['total_dirs']) + check('entries', head.get('entries'), d['total_entries']) + check('stale', head.get('stale'), d['stale_count']) + + # Category table: | config | 12 | etc. + cat_re = re.compile(r'^\|\s*([A-Za-z0-9_]+)\s*\|\s*(\d+)\s*\|', re.M) + reported_cat = {} + for m in cat_re.finditer(text): + key = m.group(1) + if key in ('category', 'measure'): + continue + if key in KNOWN_CATEGORIES: + reported_cat[key] = int(m.group(2)) + + check('category totals sum', sum(reported_cat.values()), d['total_entries']) + for k, v in d['category_counts'].items(): + check('category %s' % k, reported_cat.get(k), v) + + print('recompute files=%s dirs=%s entries=%s stale=%s' + % (d['total_files'], d['total_dirs'], d['total_entries'], d['stale_count'])) + print('recompute stale files/dirs = %s/%s' + % (d['stale_files'], d['stale_dirs'])) + + if failures: + print('VERDICT: RED (%d assertion(s) failed)' % len(failures)) + for fl in failures: + print(' FAIL', fl) + return 1 + + print('VERDICT: GREEN (%d category counts + 4 headline totals all match)' + % (len(d['category_counts']) + 4)) + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/wizard-work/sol/verify_sol_profile_triage.sh b/wizard-work/sol/verify_sol_profile_triage.sh new file mode 100644 index 0000000..95bc13b --- /dev/null +++ b/wizard-work/sol/verify_sol_profile_triage.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +# Single bare command: runs the Sol-lane profile-state triage verifier. +# Its exit code IS the verdict (0 = correct, nonzero = wrong). +set -u +python3 "$(dirname "$0")/verify_sol_profile_triage.py"