Files
settled-reach/tooling/domains/atlas/verify.py
T
jpmschweitzerandClaude Opus 5.5 c597ec9131 docs(tooling): T-1253 — sweep the live references to retired tool paths
A script scanned every tracked doc, rule, skill, agent, hook and source file
for tooling/ paths that no longer exist, skipping historical records (sprints,
discussions, workshops, governance, generated wiki pages). It found 62. The
ones that tell a reader what to RUN now name the reach verb:

- The atlas skill still sent agents to tooling/atlas, atlas-verify,
  atlas-update-field and atlas-commit-and-sync — about forty lines, all
  retired in T-1285. They now name the `reach atlas` verbs, and the skill
  records that commit-and-sync STAGES by default (--commit to commit) and
  takes --corridor as an option.
- The clerk agent named tooling/clerk-review (now `reach dev clerk`). The Si
  and clerk briefings sent those agents to the retired tooling/db/decision
  and sqlite-query CLIs and to decisions/*.md paths that moved to
  governance/ in the pql migration. They now name pql.
- The ticket-cli rule documented `pql decisions read`, which does not exist;
  `show` already includes the body.
- The culture authoring guide and the RON sources name
  `reach validate ron`, with the same arguments as before.
- The 41 Blender payloads' usage lines ran the retired tooling/blender
  wrapper, and the docstrings still cited pre-carve-out paths. They now read
  `reach blender run <payload>`.
- Doc comments in server/, client/, wiki TOMLs and the domain modules.

What is left is deliberate: "Formerly …" provenance, dated plans and findings
docs, the retired-pipeline doc, and a build-artefact path.

project.yaml 0.4.14 (mirrored to the client). Comment-only, but four touched
files are in the canvas-version registry (trait_catalog_reader.rs, since
T-1289, canvas_sources.py itself, and two client files). The gate is
path-based and has no override. The previous push was rejected on exactly
this.

Three of the edits are stamped ledger sources, so systems.db is regenerated
and the stamp is fresh.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 20:13:58 +02:00

139 lines
4.9 KiB
Python
Executable File

#!/usr/bin/env python3
"""Verify an atlas proposal JSON against integrity checks.
Usage:
reach atlas verify docs/atlas/proposals/GJ273.json
reach atlas verify docs/atlas/proposals/*.json
"""
import json
from pathlib import Path
from tooling.core import console
def verify_proposal(path: Path) -> list[str]:
"""Return list of error strings. Empty list = pass."""
with open(path) as f:
p = json.load(f)
errors = []
bodies = p.get("bodies", [])
stations = p.get("stations", [])
# 1. Inhabited bodies must have names
for b in bodies:
if b.get("inhabited") and not b.get("proper_name"):
errors.append(f"Inhabited body {b['body_id']} has no proper_name")
# 2. Uninhabited bodies MAY have names (lore reasons) — no check needed
# 3. All stations must have names
for s in stations:
if not s.get("proper_name"):
errors.append(f"Station {s['station_id']} has no proper_name")
# 4. Body count minimum by star type
planets = [b for b in bodies if b["body_type"] == "planet"]
star_type = p.get("spectral_class", "M")
if star_type.startswith(("G", "F")):
min_planets = 8
elif star_type.startswith("K"):
min_planets = 7
else:
min_planets = 6
if len(planets) < min_planets:
errors.append(
f"Only {len(planets)} planets, need {min_planets}+ "
f"for {star_type} star"
)
# 5. Required structures
if not any(b["body_type"] == "oort_cloud" for b in bodies):
errors.append("No oort cloud")
horizons = [s for s in stations if s["station_type"] == "horizon"]
if not horizons:
errors.append("No horizon station")
elif not any(s.get("has_gate_infrastructure") for s in horizons):
errors.append("Horizon station missing has_gate_infrastructure: true")
if not any(b["body_type"] == "asteroid_belt" for b in bodies):
errors.append("No asteroid belt (add one unless wiki contradicts)")
# 6. Orbit consistency — top-level bodies
top_level = [b for b in bodies if not b.get("parent_body_id")]
orbits = [b["orbit_index"] for b in top_level]
if orbits != sorted(orbits):
errors.append(f"Top-level orbit_index not monotonic: {orbits}")
if len(orbits) != len(set(orbits)):
errors.append(f"Duplicate orbit_index in top-level: {orbits}")
# 7. Moon orbit consistency
parents: dict[str, list[int]] = {}
for b in bodies:
pid = b.get("parent_body_id")
if pid:
parents.setdefault(pid, []).append(b["orbit_index"])
for pid, idxs in parents.items():
if idxs != sorted(idxs):
errors.append(f"Moon orbits under {pid} not monotonic: {idxs}")
if len(idxs) != len(set(idxs)):
errors.append(f"Duplicate moon orbit_index under {pid}: {idxs}")
# 8. Parent references valid
body_ids = {b["body_id"] for b in bodies}
for b in bodies:
pid = b.get("parent_body_id")
if pid and pid not in body_ids:
errors.append(
f"Body {b['body_id']} references missing parent {pid}"
)
for s in stations:
oid = s.get("orbits_body_id")
if oid and oid not in body_ids:
errors.append(
f"Station {s['station_id']} references missing body {oid}"
)
# 9. star_type vs spectral_class consistency
star_type_field = p.get("star_type", "")
spectral = p.get("spectral_class", "")
if star_type_field and spectral and star_type_field not in ("binary", "unusual"):
# Strip dwarf/subdwarf prefixes (d, sd) to get actual class letter
s = spectral.lstrip("sd").upper()
spectral_letter = s[0] if s else ""
if spectral_letter and spectral_letter.isalpha():
if star_type_field[0].upper() != spectral_letter:
errors.append(
f"star_type '{star_type_field}' conflicts with "
f"spectral_class '{spectral}'"
)
return errors
# Sol is hand-authored with different rules — named uninhabited bodies and so
# on — so verifying it against the generated-proposal schema would report
# failures for things that are deliberate.
EXCLUDE = {"GJ0.json", "GJ1221.json"}
def verify_all(proposals: list[Path]) -> int:
"""Verify proposal JSONs. Returns the total error count across all files."""
total = 0
for path in proposals:
if path.name in EXCLUDE:
continue
if not path.exists():
console.event(f"SKIP — {path} not found", level="warn")
continue
errors = verify_proposal(path)
if errors:
console.event(f"FAIL — {path.name}", level="error")
for error in errors:
console.event(f" - {error}", level="error")
total += len(errors)
else:
console.event(f"PASS — {path.name}")
return total