sol: sol-lane untracked-file triage classification 2026-11-02

This commit is contained in:
sol committed 2026-11-02 04:52:57 +00:00
1 parent e31c056d9e
commit ff4e2fb72e
5 files changed
+1002

No files matched your search

@@ -0,0 +1,17 @@
#!/usr/bin/env bash
# CHECK -- sol: untracked-file triage classification (2026-11-02).
# Exit 0 iff the committed manifest verifies: the manifest re-derives every
# frozen path's category/decision by the canonical rule (no lookup), every
# path is still a real, untracked, non-ignored file, the corpus sha256 matches,
# ids are 1..N, the summary counts are consistent, and the wizard-outputs shard
# is byte-identical to the repo manifest.
set -u
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Resolve the repo root (this script may live inside wizard-work/sol/ or at the
# repo root, so walk up to the directory that holds .git).
ROOT="$(git -C "$HERE" rev-parse --show-toplevel 2>/dev/null)" \
|| { echo "CANNOT GRADE: no git toplevel above $HERE" >&2; exit 2; }
cd "$ROOT" || { echo "CANNOT GRADE: cannot cd to $ROOT" >&2; exit 2; }
exec python3 wizard-work/sol/verify_untracked_triad_2026_11_02.py
@@ -0,0 +1,625 @@
{
"artifact": "Untracked-Triad-2026-11-02.json",
"built_at": "2026-11-02T04:50:51.524666+00:00",
"corpus_sha256": "144a9dd89619300d0016c490033605ff00c19d0b2a99964537127754f22503ab",
"files": [
"CHECK-2026-11-02-openmeteo-summary",
"CHECK-sol-untracked-classify",
"CHECK-triage-2026-10-29",
"checkins/2026-10-27-sentinel.md",
"d.jsonl",
"docs/clawrouter-plan.md",
"docs/flux2-deploy.md",
"docs/video-deploy.md",
"inbox/dump_seed.py",
"kanban.db",
"recoveries/apply-queue-status-2026-10-25.md",
"repo_exts.tsv",
"repo_files.tsv",
"repo_tree.tsv",
"s.json",
"test_sample.txt",
"test_validation.txt",
"test_verification.txt",
"validate_artifact.py",
"wizard-work/null/README-gatehygiene.md",
"wizard-work/null/README-q62-scan.md",
"wizard-work/null/_probe10.py",
"wizard-work/null/_probe11.py",
"wizard-work/null/_probe12.py",
"wizard-work/null/_probe13.py",
"wizard-work/null/_probe14.py",
"wizard-work/null/_probe5.py",
"wizard-work/null/_probe6.py",
"wizard-work/null/_probe7.py",
"wizard-work/null/_probe8.py",
"wizard-work/null/_probe9.py",
"wizard-work/null/_probe_binding_defects.py",
"wizard-work/null/_probe_gh1.py",
"wizard-work/null/_probe_gh2.py",
"wizard-work/null/_probe_gh3.py",
"wizard-work/null/_probe_gh4.py",
"wizard-work/null/_probe_gh5.py",
"wizard-work/null/_probe_gh6.py",
"wizard-work/null/_probe_gh7.py",
"wizard-work/null/_probe_gh8.py",
"wizard-work/null/_probe_gh9.py",
"wizard-work/null/_probe_mut2.py",
"wizard-work/null/_probe_mut_check.py",
"wizard-work/null/_probe_q70_5h.py",
"wizard-work/null/_probe_repro1.py",
"wizard-work/null/_probe_repro2.py",
"wizard-work/null/_probe_repro3.py",
"wizard-work/null/_probe_repro4.py",
"wizard-work/null/_probe_repro5.py",
"wizard-work/null/_probe_repro6.py",
"wizard-work/null/_probe_repro7.py",
"wizard-work/null/check_gatehygiene.py",
"wizard-work/null/check_provenance.py",
"wizard-work/null/check_provenance_baseline.tsv",
"wizard-work/null/check_provenance_ledger.py",
"wizard-work/null/check_q62_scan.py",
"wizard-work/null/cli_contract.py",
"wizard-work/null/diffkit/__init__.py",
"wizard-work/null/diffkit/_flatpkg.py",
"wizard-work/null/diffkit/core.py",
"wizard-work/null/diffkit/diffkit_cli.py",
"wizard-work/null/diffkit/myers.py",
"wizard-work/null/diffkit_gate.py",
"wizard-work/null/gatehygiene.py",
"wizard-work/null/json_salvage.py",
"wizard-work/null/membershipkit/.hermes-tmp.sx2gIa",
"wizard-work/null/membershipkit/check_membershipkit.sh",
"wizard-work/null/membershipkit/mutate_membershipkit.py",
"wizard-work/null/membershipkit/test_membershipkit.py",
"wizard-work/null/mutate_resolver.py",
"wizard-work/null/mutate_salvage.py",
"wizard-work/null/q58_warncontrol.py",
"wizard-work/null/q62_scan.py",
"wizard-work/null/q70_sabotage.py",
"wizard-work/null/q76_binding.py",
"wizard-work/null/q80_warn.py",
"wizard-work/null/revwalk.py",
"wizard-work/null/test_gate.py",
"wizard-work/null/test_gate_mutants.py",
"wizard-work/null/test_json_salvage.py",
"wizard-work/null/test_myers_diff.py",
"wizard-work/null/test_revwalk.py",
"wizard-work/null/verify_check_provenance.py",
"wizard-work/null/verify_staged.py",
"wizard-work/quill/draft-compliance-report-2026-10-31.pin",
"wizard-work/sphinx/archive-mmlongcite-2026-10-25/probe_paper_facts.json"
],
"items": [
{
"category": "gate",
"decision": "approve",
"id": 1,
"path": "CHECK-2026-11-02-openmeteo-summary"
},
{
"category": "gate",
"decision": "approve",
"id": 2,
"path": "CHECK-sol-untracked-classify"
},
{
"category": "gate",
"decision": "approve",
"id": 3,
"path": "CHECK-triage-2026-10-29"
},
{
"category": "documentation",
"decision": "info",
"id": 4,
"path": "checkins/2026-10-27-sentinel.md"
},
{
"category": "data",
"decision": "observe",
"id": 5,
"path": "d.jsonl"
},
{
"category": "documentation",
"decision": "info",
"id": 6,
"path": "docs/clawrouter-plan.md"
},
{
"category": "documentation",
"decision": "info",
"id": 7,
"path": "docs/flux2-deploy.md"
},
{
"category": "documentation",
"decision": "info",
"id": 8,
"path": "docs/video-deploy.md"
},
{
"category": "tooling",
"decision": "review",
"id": 9,
"path": "inbox/dump_seed.py"
},
{
"category": "persistence",
"decision": "archive",
"id": 10,
"path": "kanban.db"
},
{
"category": "documentation",
"decision": "info",
"id": 11,
"path": "recoveries/apply-queue-status-2026-10-25.md"
},
{
"category": "data",
"decision": "observe",
"id": 12,
"path": "repo_exts.tsv"
},
{
"category": "data",
"decision": "observe",
"id": 13,
"path": "repo_files.tsv"
},
{
"category": "data",
"decision": "observe",
"id": 14,
"path": "repo_tree.tsv"
},
{
"category": "data",
"decision": "observe",
"id": 15,
"path": "s.json"
},
{
"category": "data",
"decision": "observe",
"id": 16,
"path": "test_sample.txt"
},
{
"category": "data",
"decision": "observe",
"id": 17,
"path": "test_validation.txt"
},
{
"category": "data",
"decision": "observe",
"id": 18,
"path": "test_verification.txt"
},
{
"category": "tooling",
"decision": "review",
"id": 19,
"path": "validate_artifact.py"
},
{
"category": "documentation",
"decision": "info",
"id": 20,
"path": "wizard-work/null/README-gatehygiene.md"
},
{
"category": "documentation",
"decision": "info",
"id": 21,
"path": "wizard-work/null/README-q62-scan.md"
},
{
"category": "tooling",
"decision": "review",
"id": 22,
"path": "wizard-work/null/_probe10.py"
},
{
"category": "tooling",
"decision": "review",
"id": 23,
"path": "wizard-work/null/_probe11.py"
},
{
"category": "tooling",
"decision": "review",
"id": 24,
"path": "wizard-work/null/_probe12.py"
},
{
"category": "tooling",
"decision": "review",
"id": 25,
"path": "wizard-work/null/_probe13.py"
},
{
"category": "tooling",
"decision": "review",
"id": 26,
"path": "wizard-work/null/_probe14.py"
},
{
"category": "tooling",
"decision": "review",
"id": 27,
"path": "wizard-work/null/_probe5.py"
},
{
"category": "tooling",
"decision": "review",
"id": 28,
"path": "wizard-work/null/_probe6.py"
},
{
"category": "tooling",
"decision": "review",
"id": 29,
"path": "wizard-work/null/_probe7.py"
},
{
"category": "tooling",
"decision": "review",
"id": 30,
"path": "wizard-work/null/_probe8.py"
},
{
"category": "tooling",
"decision": "review",
"id": 31,
"path": "wizard-work/null/_probe9.py"
},
{
"category": "tooling",
"decision": "review",
"id": 32,
"path": "wizard-work/null/_probe_binding_defects.py"
},
{
"category": "tooling",
"decision": "review",
"id": 33,
"path": "wizard-work/null/_probe_gh1.py"
},
{
"category": "tooling",
"decision": "review",
"id": 34,
"path": "wizard-work/null/_probe_gh2.py"
},
{
"category": "tooling",
"decision": "review",
"id": 35,
"path": "wizard-work/null/_probe_gh3.py"
},
{
"category": "tooling",
"decision": "review",
"id": 36,
"path": "wizard-work/null/_probe_gh4.py"
},
{
"category": "tooling",
"decision": "review",
"id": 37,
"path": "wizard-work/null/_probe_gh5.py"
},
{
"category": "tooling",
"decision": "review",
"id": 38,
"path": "wizard-work/null/_probe_gh6.py"
},
{
"category": "tooling",
"decision": "review",
"id": 39,
"path": "wizard-work/null/_probe_gh7.py"
},
{
"category": "tooling",
"decision": "review",
"id": 40,
"path": "wizard-work/null/_probe_gh8.py"
},
{
"category": "tooling",
"decision": "review",
"id": 41,
"path": "wizard-work/null/_probe_gh9.py"
},
{
"category": "tooling",
"decision": "review",
"id": 42,
"path": "wizard-work/null/_probe_mut2.py"
},
{
"category": "tooling",
"decision": "review",
"id": 43,
"path": "wizard-work/null/_probe_mut_check.py"
},
{
"category": "tooling",
"decision": "review",
"id": 44,
"path": "wizard-work/null/_probe_q70_5h.py"
},
{
"category": "tooling",
"decision": "review",
"id": 45,
"path": "wizard-work/null/_probe_repro1.py"
},
{
"category": "tooling",
"decision": "review",
"id": 46,
"path": "wizard-work/null/_probe_repro2.py"
},
{
"category": "tooling",
"decision": "review",
"id": 47,
"path": "wizard-work/null/_probe_repro3.py"
},
{
"category": "tooling",
"decision": "review",
"id": 48,
"path": "wizard-work/null/_probe_repro4.py"
},
{
"category": "tooling",
"decision": "review",
"id": 49,
"path": "wizard-work/null/_probe_repro5.py"
},
{
"category": "tooling",
"decision": "review",
"id": 50,
"path": "wizard-work/null/_probe_repro6.py"
},
{
"category": "tooling",
"decision": "review",
"id": 51,
"path": "wizard-work/null/_probe_repro7.py"
},
{
"category": "tooling",
"decision": "review",
"id": 52,
"path": "wizard-work/null/check_gatehygiene.py"
},
{
"category": "tooling",
"decision": "review",
"id": 53,
"path": "wizard-work/null/check_provenance.py"
},
{
"category": "data",
"decision": "observe",
"id": 54,
"path": "wizard-work/null/check_provenance_baseline.tsv"
},
{
"category": "tooling",
"decision": "review",
"id": 55,
"path": "wizard-work/null/check_provenance_ledger.py"
},
{
"category": "tooling",
"decision": "review",
"id": 56,
"path": "wizard-work/null/check_q62_scan.py"
},
{
"category": "tooling",
"decision": "review",
"id": 57,
"path": "wizard-work/null/cli_contract.py"
},
{
"category": "tooling",
"decision": "review",
"id": 58,
"path": "wizard-work/null/diffkit/__init__.py"
},
{
"category": "tooling",
"decision": "review",
"id": 59,
"path": "wizard-work/null/diffkit/_flatpkg.py"
},
{
"category": "tooling",
"decision": "review",
"id": 60,
"path": "wizard-work/null/diffkit/core.py"
},
{
"category": "tooling",
"decision": "review",
"id": 61,
"path": "wizard-work/null/diffkit/diffkit_cli.py"
},
{
"category": "tooling",
"decision": "review",
"id": 62,
"path": "wizard-work/null/diffkit/myers.py"
},
{
"category": "tooling",
"decision": "review",
"id": 63,
"path": "wizard-work/null/diffkit_gate.py"
},
{
"category": "tooling",
"decision": "review",
"id": 64,
"path": "wizard-work/null/gatehygiene.py"
},
{
"category": "tooling",
"decision": "review",
"id": 65,
"path": "wizard-work/null/json_salvage.py"
},
{
"category": "other",
"decision": "review",
"id": 66,
"path": "wizard-work/null/membershipkit/.hermes-tmp.sx2gIa"
},
{
"category": "tooling",
"decision": "review",
"id": 67,
"path": "wizard-work/null/membershipkit/check_membershipkit.sh"
},
{
"category": "tooling",
"decision": "review",
"id": 68,
"path": "wizard-work/null/membershipkit/mutate_membershipkit.py"
},
{
"category": "tooling",
"decision": "review",
"id": 69,
"path": "wizard-work/null/membershipkit/test_membershipkit.py"
},
{
"category": "tooling",
"decision": "review",
"id": 70,
"path": "wizard-work/null/mutate_resolver.py"
},
{
"category": "tooling",
"decision": "review",
"id": 71,
"path": "wizard-work/null/mutate_salvage.py"
},
{
"category": "tooling",
"decision": "review",
"id": 72,
"path": "wizard-work/null/q58_warncontrol.py"
},
{
"category": "tooling",
"decision": "review",
"id": 73,
"path": "wizard-work/null/q62_scan.py"
},
{
"category": "tooling",
"decision": "review",
"id": 74,
"path": "wizard-work/null/q70_sabotage.py"
},
{
"category": "tooling",
"decision": "review",
"id": 75,
"path": "wizard-work/null/q76_binding.py"
},
{
"category": "tooling",
"decision": "review",
"id": 76,
"path": "wizard-work/null/q80_warn.py"
},
{
"category": "tooling",
"decision": "review",
"id": 77,
"path": "wizard-work/null/revwalk.py"
},
{
"category": "tooling",
"decision": "review",
"id": 78,
"path": "wizard-work/null/test_gate.py"
},
{
"category": "tooling",
"decision": "review",
"id": 79,
"path": "wizard-work/null/test_gate_mutants.py"
},
{
"category": "tooling",
"decision": "review",
"id": 80,
"path": "wizard-work/null/test_json_salvage.py"
},
{
"category": "tooling",
"decision": "review",
"id": 81,
"path": "wizard-work/null/test_myers_diff.py"
},
{
"category": "tooling",
"decision": "review",
"id": 82,
"path": "wizard-work/null/test_revwalk.py"
},
{
"category": "tooling",
"decision": "review",
"id": 83,
"path": "wizard-work/null/verify_check_provenance.py"
},
{
"category": "tooling",
"decision": "review",
"id": 84,
"path": "wizard-work/null/verify_staged.py"
},
{
"category": "other",
"decision": "review",
"id": 85,
"path": "wizard-work/quill/draft-compliance-report-2026-10-31.pin"
},
{
"category": "data",
"decision": "observe",
"id": 86,
"path": "wizard-work/sphinx/archive-mmlongcite-2026-10-25/probe_paper_facts.json"
}
],
"lane": "Triage - everyday Q&A and classification",
"repo": "/root/troupe-standup",
"rule": "path-based deterministic classifier (see MODULE_DOCLINE)",
"rule_version": "1",
"summary": {
"data": 10,
"documentation": 7,
"gate": 3,
"other": 2,
"persistence": 1,
"tooling": 63,
"total": 86
},
"task": "classify untracked repo files into deterministic buckets"
}
@@ -0,0 +1,22 @@
# Untracked-file triage classification — 2026-11-02
Triaged **86** untracked, non-ignored files under `/root/troupe-standup`.
## Bucket counts
| category | decision | count |
|---|---|---|
| gate | approve | 3 |
| persistence | archive | 1 |
| data | observe | 10 |
| tooling | review | 63 |
| documentation | info | 7 |
| other | review | 2 |
## Notes
- Rule: path-based deterministic classifier (see MODULE_DOCLINE).
- `corpus_sha256` is a sha256 over the canonical file list;
the independent verifier re-derives this, not the generator.
- Every classified path is re-checked as a real, untracked,
non-ignored file at run time; no path is trusted from memory.
@@ -0,0 +1,190 @@
#!/usr/bin/env python3
"""Build Untracked-Triad-2026-11-02.json.
Triage: classify the untracked (git ??) regular files of the repo into
deterministic buckets, then write the machine-readable artifact.
Rule (deterministic, derived from the relative path only):
category:
gate path starts with "CHECK-"
persistence ends with ".db"
data ends with ".json", ".jsonl", ".tsv" or ".txt"
tooling ends with ".py" or ".sh"
documentation ends with ".md"
other anything else
decision:
gate -> approve
tooling -> review
documentation -> info
data -> observe
persistence -> archive
other -> review
The manifest snapshots the exact file list at generation time (files + a
sha256 of the canonical list) so the independent verifier can confirm the
artifact is a faithful, reproducible classification, not a hand-edited guess.
Outputs
-------
wizard-work/sol/Untracked-Triad-2026-11-02.json machine-readable manifest
wizard-work/sol/Untracked-Triad-2026-11-02.md human-readable summary
/root/.hermes/wizard-outputs/sol/Untracked-Triad-2026-11-02.json
identical shard read by the triage harness
Run: python3 build_untracked_triad_2026_11_02.py
"""
import hashlib
import json
import os
import shutil
import subprocess
from datetime import datetime, timezone
SOL_DIR = os.path.dirname(os.path.abspath(__file__))
REPO_ROOT = os.path.dirname(os.path.dirname(SOL_DIR))
OUT_REPO = os.path.join(SOL_DIR, "Untracked-Triad-2026-11-02.json")
OUT_MD = os.path.join(SOL_DIR, "Untracked-Triad-2026-11-02.md")
OUT_WIZ = os.path.expanduser(
os.path.join("~", ".hermes", "wizard-outputs", "sol", "Untracked-Triad-2026-11-02.json")
)
# --------------------------------------------------------------------------- #
# universe & classification
# --------------------------------------------------------------------------- #
def git_untracked(repo):
"""Return sorted list of untracked regular files, excluding this pipeline's
own files (wizard-work/sol/ subtree) and the CHECK gate so the snapshot is
a stable, reproducible classification rather than self-referential."""
proc = subprocess.run(
["git", "-C", repo, "status", "--porcelain=v1"],
check=True, capture_output=True, text=True,
)
files = []
for line in proc.stdout.splitlines():
if line.startswith("??"):
path = line[3:].strip()
if path.endswith("/"):
continue # untracked directories
if path.startswith("wizard-work/sol/"):
continue # this pipeline's own files (incl. manifest/verifier/build)
if os.path.basename(path) == "CHECK-2026-11-02-untracked-triad":
continue # the CHECK gate, committed alongside the manifest
files.append(path)
return sorted(files)
def category_of(rel):
low = rel.lower()
if low.startswith("check-"):
return "gate"
if low.endswith(".db"):
return "persistence"
if low.endswith((".json", ".jsonl", ".tsv", ".txt")):
return "data"
if low.endswith((".py", ".sh")):
return "tooling"
if low.endswith(".md"):
return "documentation"
return "other"
DECISION = {
"gate": "approve",
"tooling": "review",
"documentation": "info",
"data": "observe",
"persistence": "archive",
"other": "review",
}
def build(files):
items = []
for i, rel in enumerate(files, start=1):
cat = category_of(rel)
items.append({"id": i, "path": rel, "category": cat, "decision": DECISION[cat]})
return items
def corpus_sha256(files):
canon = json.dumps({"files": files}, sort_keys=True, separators=(",", ":")).encode("utf-8")
return hashlib.sha256(canon).hexdigest()
# --------------------------------------------------------------------------- #
# output writers
# --------------------------------------------------------------------------- #
def write_json(obj, path):
with open(path, "w", encoding="utf-8") as fh:
json.dump(obj, fh, indent=2, sort_keys=True)
fh.write("\n")
def write_md(manifest, path):
s = manifest["summary"]
lines = []
lines.append("# Untracked-file triage classification — 2026-11-02")
lines.append("")
lines.append("Triaged **%d** untracked, non-ignored files under `%s`." % (
s.get("total", 0), manifest["repo"]))
lines.append("")
lines.append("## Bucket counts")
lines.append("")
lines.append("| category | decision | count |")
lines.append("|---|---|---|")
for cat in ["gate", "persistence", "data", "tooling", "documentation", "other"]:
lines.append("| %s | %s | %d |" % (
cat, DECISION[cat], s.get(cat, 0)))
lines.append("")
lines.append("## Notes")
lines.append("")
lines.append("- Rule: path-based deterministic classifier (see MODULE_DOCLINE).")
lines.append("- `corpus_sha256` is a sha256 over the canonical file list;")
lines.append(" the independent verifier re-derives this, not the generator.")
lines.append("- Every classified path is re-checked as a real, untracked,")
lines.append(" non-ignored file at run time; no path is trusted from memory.")
lines.append("")
with open(path, "w", encoding="utf-8") as fh:
fh.write("\n".join(lines))
# --------------------------------------------------------------------------- #
# main
# --------------------------------------------------------------------------- #
def main():
files = git_untracked(REPO_ROOT)
items = build(files)
summary = {"total": len(files)}
for it in items:
summary[it["category"]] = summary.get(it["category"], 0) + 1
manifest = {
"artifact": "Untracked-Triad-2026-11-02.json",
"corpus_sha256": corpus_sha256(files),
"files": files,
"items": items,
"lane": "Triage - everyday Q&A and classification",
"repo": REPO_ROOT,
"rule": "path-based deterministic classifier (see MODULE_DOCLINE)",
"rule_version": "1",
"summary": summary,
"task": "classify untracked repo files into deterministic buckets",
"built_at": datetime.now(timezone.utc).isoformat(),
}
write_json(manifest, OUT_REPO)
write_md(manifest, OUT_MD)
# identical shard to the triage harness
shutil.copyfile(OUT_REPO, OUT_WIZ)
with open(OUT_REPO, "rb") as a, open(OUT_WIZ, "rb") as b:
if a.read() != b.read():
sys.exit("shard parity mismatch: %s vs %s" % (OUT_REPO, OUT_WIZ))
print("wrote: %s (%d files)" % (OUT_REPO, len(files)))
print("wrote: %s" % OUT_MD)
print("shard: %s" % OUT_WIZ)
print("buckets: %s" % json.dumps(summary, sort_keys=True))
if __name__ == "__main__":
main()
@@ -0,0 +1,148 @@
#!/usr/bin/env python3
"""Independent verifier for Untracked-Triad-2026-11-02.json.
The classification rule is re-derived from each item's path (the generator's
stored category/decision fields are NOT trusted). A wrong classification makes
this exit non-zero. Additionally every classified path must still exist on disk
as a real, untracked, non-ignored file, and the wizard-outputs shard must
match the repo manifest byte-for-byte.
Exit 0 iff all checks pass.
"""
import hashlib
import json
import os
import subprocess
import sys
HERE = os.path.dirname(os.path.abspath(__file__))
MANIFEST = os.path.join(HERE, "Untracked-Triad-2026-11-02.json")
OUT_WIZ = os.path.expanduser(
os.path.join("~", ".hermes", "wizard-outputs", "sol", "Untracked-Triad-2026-11-02.json")
)
REPO_ROOT = os.path.dirname(os.path.dirname(HERE))
def cat_of(rel):
low = rel.lower()
if low.startswith("check-"):
return "gate"
if low.endswith(".db"):
return "persistence"
if low.endswith((".json", ".jsonl", ".tsv", ".txt")):
return "data"
if low.endswith((".py", ".sh")):
return "tooling"
if low.endswith(".md"):
return "documentation"
return "other"
DECISION = {
"gate": "approve",
"tooling": "review",
"documentation": "info",
"data": "observe",
"persistence": "archive",
"other": "review",
}
def git_status_untracked(repo):
proc = subprocess.run(
["git", "-C", repo, "status", "--porcelain=v1"], capture_output=True, text=True)
return set(line[3:].strip() for line in proc.stdout.splitlines()
if line.startswith("??") and not line[3:].strip().endswith("/"))
def git_check_ignored(repo, rel):
proc = subprocess.run(["git", "-C", repo, "check-ignore", "-q", rel], capture_output=True)
return proc.returncode == 0
def main():
errors = []
try:
with open(MANIFEST, encoding="utf-8") as fh:
man = json.load(fh)
except Exception as exc:
print("FAIL: cannot load manifest:", exc)
sys.exit(1)
files = man.get("files", [])
items = man.get("items", [])
repo = man.get("repo", REPO_ROOT)
recorded_sha = man.get("corpus_sha256")
# 1. corpus sha256 must match the recorded file list
canon = json.dumps({"files": sorted(files)}, sort_keys=True,
separators=(",", ":")).encode("utf-8")
computed_sha = hashlib.sha256(canon).hexdigest()
if computed_sha != recorded_sha:
errors.append("corpus_sha256 mismatch (recorded %s vs computed %s)"
% (recorded_sha, computed_sha))
# 2. ids must be unique and exactly 1..N
ids = [it.get("id") for it in items]
if sorted(ids) != list(range(1, len(items) + 1)):
errors.append("ids are not exactly 1..N")
# 3. re-derive category/decision from each path and verify file state
for it in items:
rel = it.get("path")
exp_cat = it.get("category")
exp_dec = it.get("decision")
if not rel:
errors.append("missing path on item id=%r" % (it.get("id"),))
continue
act_cat = cat_of(rel)
act_dec = DECISION.get(act_cat)
if exp_cat != act_cat:
errors.append("id=%s path=%s category %s != re-derived %s"
% (it.get("id"), rel, exp_cat, act_cat))
if exp_dec != act_dec:
errors.append("id=%s path=%s decision %s != re-derived %s"
% (it.get("id"), rel, exp_dec, act_dec))
if not os.path.isfile(os.path.join(repo, rel)):
errors.append("id=%s path=%s does not exist as a real file"
% (it.get("id"), rel))
if rel not in git_status_untracked(repo):
errors.append("id=%s path=%s is not currently untracked"
% (it.get("id"), rel))
if git_check_ignored(repo, rel):
errors.append("id=%s path=%s is git-ignored" % (it.get("id"), rel))
# 4. summary counts must match items (skip the "total" key, which is the
# corpus size, not a category).
from collections import Counter
CAT_KEYS = {"gate", "persistence", "data", "tooling", "documentation", "other"}
cnt = Counter(it.get("category") for it in items)
for cat, n in man.get("summary", {}).items():
if cat not in CAT_KEYS:
continue
if cnt.get(cat, 0) != n:
errors.append("summary %s=%d but items have %d"
% (cat, n, cnt.get(cat, 0)))
# 5. shard parity: the wizard-outputs copy must be byte-identical.
if os.path.exists(OUT_WIZ):
with open(MANIFEST, "rb") as a, open(OUT_WIZ, "rb") as b:
if a.read() != b.read():
errors.append("shard parity FAIL: %s != %s" % (MANIFEST, OUT_WIZ))
else:
errors.append("shard missing: %s" % OUT_WIZ)
if errors:
print("FAIL: %d problem(s)" % len(errors))
for e in errors[:40]:
print(" -", e)
sys.exit(1)
print("PASS: %d files classified; %d re-derive to matching category/decision; "
"all files real, untracked, non-ignored; corpus sha256 matches; "
"shard byte-identical." % (len(files), len(items)))
sys.exit(0)
if __name__ == "__main__":
main()