#!/usr/bin/env python3 # ============================================================================= # Project : anomaly-atlas # File : tools/new_finding.py # Purpose : Scaffold a compliant atlas entry — refuses incomplete provenance # Author : Simon-Pierre Boucher # Contact : contact@spboucher.ai # Data src : hfmarketdata.io (sole data source) # Created : 2026-08-12 # Modified : 2026-08-12 # Platform : macOS / Apple Silicon (arm64) # License : All rights reserved (research code) # ============================================================================= """Create atlas/// from a completed finding payload. Refuses to create an entry unless BOTH a complete provenance.json and a confidence.md are supplied (CLAUDE.md §3, §10). Level 0 findings are refused outright — in-sample-only output never enters the atlas. Usage: python3 tools/new_finding.py \\ --finding finding.json --provenance provenance.json --confidence confidence.md Required provenance keys: commit, config, data_manifest_hash, hardware_manifest, generated_utc, n_hypotheses_tested, correction_method, oos_status Required confidence.md content: a 'Level: <1|2|3>' line plus front matter. """ from __future__ import annotations import argparse import json import re import shutil import sys from pathlib import Path REPO_ROOT = Path(__file__).resolve().parent.parent PROVENANCE_KEYS = ( "commit", "config", "data_manifest_hash", "hardware_manifest", "generated_utc", "n_hypotheses_tested", "correction_method", "oos_status", ) FINDING_ATTRIBUTION = { "author": "Simon-Pierre Boucher", "contact": "contact@spboucher.ai", "data_source": "hfmarketdata.io", } def fail(msg: str) -> None: sys.exit(f"REFUSED: {msg}") def validate_provenance(path: Path) -> dict: try: prov = json.loads(path.read_text()) except (OSError, json.JSONDecodeError) as exc: fail(f"provenance.json unreadable/invalid: {exc}") missing = [k for k in PROVENANCE_KEYS if not prov.get(k)] if missing: fail(f"provenance.json incomplete — missing/empty: {', '.join(missing)}") return prov def validate_confidence(path: Path) -> int: try: text = path.read_text() except OSError as exc: fail(f"confidence.md unreadable: {exc}") m = re.search(r"^Level:\s*([0-3])\b", text, re.MULTILINE) if not m: fail("confidence.md must contain a 'Level: <0-3>' line with evidence") level = int(m.group(1)) if level < 1: fail("Level 0 (in-sample only) never enters the atlas — scan output only") return level def main() -> None: ap = argparse.ArgumentParser(description=__doc__) ap.add_argument("anomaly_id") ap.add_argument("version") ap.add_argument("--finding", required=True, type=Path) ap.add_argument("--provenance", required=True, type=Path) ap.add_argument("--confidence", required=True, type=Path) args = ap.parse_args() prov = validate_provenance(args.provenance) level = validate_confidence(args.confidence) try: finding = json.loads(args.finding.read_text()) except (OSError, json.JSONDecodeError) as exc: fail(f"finding.json unreadable/invalid: {exc}") finding.update(FINDING_ATTRIBUTION) finding.setdefault("anomaly_id", args.anomaly_id) finding["confidence_level"] = level dest = REPO_ROOT / "atlas" / args.anomaly_id / args.version if dest.exists(): fail(f"{dest.relative_to(REPO_ROOT)} already exists — bump the version") dest.mkdir(parents=True) (dest / "finding.json").write_text(json.dumps(finding, indent=2) + "\n") (dest / "provenance.json").write_text(json.dumps(prov, indent=2) + "\n") shutil.copy(args.confidence, dest / "confidence.md") print(f"atlas entry created: {dest.relative_to(REPO_ROOT)} (Level {level})") if __name__ == "__main__": main()