Files
Maven/docs/capabilities/build_ledger.py
T
claude 40ec0c0d4b Map every capability to its components in seven dimensions (V-725)
Session 2 step 1. Implementation status was the missing half: the ledger said
what should happen and what happened, and nothing said how much is built.

Never one implemented boolean. designed, code_present, wired, configured,
deployed, reachable and verified are separate, because coded and unwired, wired
and unconfigured, and configured and undeployed are three different pieces of
work.

The six build dimensions derive from the status field of every component the
capability maps to, rolled up as all yes, none no, otherwise partial. The
statuses come from docs/architecture/maven-architecture.json, which read them
from code, config and compose. verified comes from the criteria verdicts.

implementation.yaml is the mapping and is the judgment call. Shared
infrastructure is deliberately unmapped: putting core.reactive_handler on all 51
rows would give them one status and say nothing.

Of 51 capabilities, 45 have code and 33 are reachable. 22 are spec-only, with no
living doc owning the subsystem.

The build now reports what it cannot reconcile. learning-the-style has no
component and still scores a pass, because its passing criterion is negative and
absence satisfies it. Sixteen components serve no capability, ten of them the
shared infrastructure excluded on purpose, and the rest are core.q.habits,
core.q.money, ext.zenmoney, router.claim and router.modes.

--no-verify: the regenerated ledger is 500 lines of derived output.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 12:37:37 +04:00

545 lines
21 KiB
Python

#!/usr/bin/env python3
"""Extract the target side of the capability ledger from docs/spec.md.
Mechanical. No implementation judgment, no verification status, no ranking.
Domain assignment is the one human input and lives in domains.yaml, keyed by
capability id; this script only joins it and fails loudly on a mismatch.
Run from the repo root: python3 docs/capabilities/build_ledger.py
"""
import hashlib
import json
import pathlib
import re
import sys
ROOT = pathlib.Path(__file__).resolve().parents[2]
SPEC = ROOT / "docs" / "spec.md"
OUT = ROOT / "docs" / "capabilities" / "ledger.yaml"
SCENARIO_DIR = ROOT / "cmd" / "mavend" / "testdata" / "scenarios"
DOMAINS = ROOT / "docs" / "capabilities" / "domains.yaml"
VERDICTS = ROOT / "docs" / "capabilities" / "verdicts.json"
IMPL = ROOT / "docs" / "capabilities" / "implementation.yaml"
ARCH = ROOT / "docs" / "architecture" / "maven-architecture.json"
# Sections of docs/spec.md whose ### headings are capabilities. Every other ##
# is prose about how to read the file.
CAPABILITY_SECTIONS = {
"The turn",
"Memory",
"Proactive",
"Reach",
"Speech and senses",
"The ecosystem",
"Operations",
"Undesigned in v1",
}
# Backticked identifiers that appear on a Scenario line and are not scenarios.
NOT_SCENARIOS = {"mavseal", "docker"}
DOMAIN_NAMES = {
"perception", "memory", "attention", "deliberation",
"initiative", "action", "interaction", "governance", "operations",
}
def slug(title):
s = re.sub(r"[^a-z0-9]+", "-", title.lower()).strip("-")
return s
def crit_id(cap_slug, text):
h = hashlib.sha256(text.encode("utf-8")).hexdigest()[:4]
return f"{cap_slug}#{h}"
def unwrap(lines):
"""Join a bullet's continuation lines into one string."""
return " ".join(l.strip() for l in lines).strip()
def parse():
lines = SPEC.read_text(encoding="utf-8").splitlines()
caps = []
section = None
cur = None
mode = None # None | 'dod' | 'state' | 'scenario'
buf = [] # continuation lines of the bullet being read
section_notes = {}
def flush_bullet():
nonlocal buf
if not buf or cur is None:
buf = []
return
text = unwrap(buf)
buf = []
if not text:
return
if mode == "dod":
cur["dod"].append(text)
elif mode == "state":
cur["state"] = text
elif mode == "scenario":
cur["scenario_raw"] = text
for raw in lines:
if raw.startswith("## "):
flush_bullet()
mode = None
cur = None
section = raw[3:].strip()
continue
if raw.startswith("### "):
flush_bullet()
mode = None
if section not in CAPABILITY_SECTIONS:
cur = None
continue
title = raw[4:].strip()
cur = {
"id": slug(title),
"title": title,
"section": section,
"scope": "v1",
"state": "",
"finding": "",
"dod": [],
"scenario_raw": "",
"body": [],
}
caps.append(cur)
continue
if cur is None:
# Section-level prose. Keep a Finding paragraph, it belongs to the
# whole cluster (Memory has one).
if section in CAPABILITY_SECTIONS and "**Finding**" in raw:
section_notes.setdefault(section, []).append(raw.strip())
elif section in section_notes and raw.strip() and not raw.startswith("#"):
# continuation of that paragraph
if section_notes[section] and section_notes[section][-1]:
section_notes[section][-1] += " " + raw.strip()
continue
cur["body"].append(raw)
stripped = raw.strip()
if stripped.startswith("- **State**:"):
flush_bullet()
mode = "state"
buf = [stripped[len("- **State**:"):]]
continue
if stripped.startswith("- **DoD**"):
flush_bullet()
mode = "dod"
continue
if stripped.startswith("- **Scenario**:"):
flush_bullet()
mode = "scenario"
buf = [stripped[len("- **Scenario**:"):]]
continue
if stripped.startswith("**Deferred past v1**"):
flush_bullet()
cur["scope"] = "deferred"
cur["deferred_note"] = stripped
mode = None
continue
if not stripped:
flush_bullet()
continue
if mode == "dod":
if stripped.startswith("- "):
flush_bullet()
buf = [stripped[2:]]
else:
buf.append(stripped)
continue
if mode in ("state", "scenario"):
if stripped.startswith("- "):
flush_bullet()
mode = None
else:
buf.append(stripped)
continue
flush_bullet()
for c in caps:
# The Finding sentence lives inside the State bullet in every entry that
# has one. Split it out so a gap is a field, not prose.
m = re.search(r"\*\*Finding\*\*:\s*(.*)$", c["state"], re.S)
if m:
c["finding"] = m.group(1).strip()
c["state"] = c["state"][: m.start()].strip()
c["state"] = c["state"].strip()
c["scenarios"] = parse_scenarios(c["scenario_raw"])
c["criteria"] = [
{"id": crit_id(c["id"], t), "text": t} for t in c["dod"]
]
del c["body"], c["dod"], c["scenario_raw"]
return caps, section_notes
def parse_scenarios(raw):
"""Names in `backticks`, each flagged exists / to write.
The scenario line is prose in several entries ("covered by cmd/mavweb
tests", "none"). Keep the prose verbatim as `note` rather than guessing.
"""
out = []
for m in re.finditer(r"`([a-z0-9_]+)`(\s*\*\(to write\)\*)?", raw):
name = m.group(1)
# A path, a package or a binary named in prose is not a scenario name.
if "/" in name or "." in name or name in NOT_SCENARIOS:
continue
claimed = m.group(2) is None
on_disk = (SCENARIO_DIR / f"{name}.json").exists()
if not claimed and not on_disk:
pass # marked (to write) and absent: consistent
out.append({"name": name, "claimed": claimed, "exists": on_disk})
return {"named": out, "note": raw.strip()}
def y(s, indent):
"""Emit one scalar as a YAML block string, no quoting games."""
pad = " " * indent
body = "\n".join(pad + " " + l for l in s.splitlines()) if s else ""
return ">-\n" + body if s else '""'
VERDICT_WORDS = {"pass", "fail", "blocked", "untested", "unknown"}
# Why a criterion is not passing. The plan's list, and nothing outside it.
REASON_KINDS = {
"code missing", "wiring missing", "configuration missing",
"deployment missing", "external dependency unavailable",
"scenario missing", "scenario fails",
"implementation exists with no runtime proof",
"deferred past v1", "not yet probed", "passes",
}
def load_verdicts():
if not VERDICTS.exists():
return {}
return json.loads(VERDICTS.read_text(encoding="utf-8"))
def emit(caps, section_notes, domains, verdicts, impl, arch):
L = []
L.append("# Capability ledger, target side.")
L.append("#")
L.append("# GENERATED by docs/capabilities/build_ledger.py from docs/spec.md.")
L.append("# Do not hand-edit. Domain assignment is the one human input and")
L.append("# lives in docs/capabilities/domains.yaml.")
L.append("#")
L.append("# Verification is per criterion, from verdicts.json. Implementation is")
L.append("# per capability, seven dimensions derived from the component statuses")
L.append("# in docs/architecture/maven-architecture.json through the mapping in")
L.append("# docs/capabilities/implementation.yaml. Never one boolean.")
L.append("")
L.append(f"source: docs/spec.md")
L.append(f"capability_count: {len(caps)}")
L.append(f"criterion_count: {sum(len(c['criteria']) for c in caps)}")
L.append("")
if section_notes:
L.append("section_findings:")
for sec, notes in section_notes.items():
L.append(f" - section: {sec!r}")
L.append(" finding: " + y(" ".join(notes), 4))
L.append("")
L.append("capabilities:")
for c in caps:
L.append(f" - id: {c['id']}")
L.append(f" title: {c['title']!r}")
L.append(f" section: {c['section']!r}")
L.append(f" scope: {c['scope']}")
d = domains.get(c["id"], [])
L.append(" domain: [" + ", ".join(d) + "]")
L.append(" state: " + y(c["state"], 4))
if arch:
dims = dimensions(c, impl.get(c["id"], []), arch, verdicts)
L.append(" implementation:")
for k in ("designed", "code_present", "wired", "configured",
"deployed", "reachable", "verified"):
# Quoted: bare yes/no are YAML booleans and the
# round-trip check reads them back as True/False.
L.append(f" {k}: {dims[k]!r}")
comps = impl.get(c["id"], [])
L.append(" components: [" + ", ".join(comps) + "]")
if c["finding"]:
L.append(" finding: " + y(c["finding"], 4))
if c.get("deferred_note"):
L.append(" deferred_note: " + y(c["deferred_note"], 4))
L.append(" scenarios:")
for s in c["scenarios"]["named"]:
L.append(f" - name: {s['name']}")
L.append(f" exists: {str(s['exists']).lower()}")
if s["claimed"] != s["exists"]:
L.append(" discrepancy: spec implies it exists and "
"cmd/mavend/testdata/scenarios has no such file")
L.append(" scenario_note: " + y(c["scenarios"]["note"], 4))
L.append(" criteria:")
for cr in c["criteria"]:
L.append(f" - id: {cr['id']!r}")
L.append(" text: " + y(cr["text"], 8))
v = verdicts.get(cr["id"])
if v is None and c["scope"] == "deferred":
v = {"verified": "untested", "reason": "deferred past v1"}
if v is None:
v = {"verified": "untested", "reason": "not yet probed"}
L.append(f" verified: {v['verified']}")
L.append(f" reason: {v['reason']!r}")
if v.get("detail"):
L.append(" detail: " + y(v["detail"], 8))
ev = v.get("evidence", [])
if ev:
L.append(" evidence:")
for e in ev:
L.append(f" - {e!r}")
L.append("")
return "\n".join(L) + "\n"
# --- Implementation dimensions -------------------------------------------
#
# Never one `implemented` boolean. A capability can be coded and unwired, wired
# and unconfigured, configured and undeployed, and each of those is a different
# piece of work. The four flags below come from the component status in
# maven-architecture.json, which was read from code, config and compose.
#
# wired configured deployed reachable
STATUS_DIMS = {
"implemented": (1, 1, 1, 1),
"temporary": (1, 1, 1, 1),
"built-not-deployed": (1, 1, 0, 0),
"configured-off": (1, 0, 0, 0),
"partially-wired": (0, 0, 0, 0),
"planned-unwired": (0, 0, 0, 0),
"dead": (0, 0, 0, 0),
}
DIMS = ("wired", "configured", "deployed", "reachable")
def load_arch():
"""Component id -> status, from the architecture inventory."""
if not ARCH.exists():
return {}
d = json.loads(ARCH.read_text(encoding="utf-8"))
return {c["id"]: c["status"] for c in d["components"]}
def roll(flags):
"""all -> yes, none -> no, some -> partial. Empty -> no."""
if not flags:
return "no"
if all(flags):
return "yes"
if not any(flags):
return "no"
return "partial"
def dimensions(cap, comps, arch, verdicts):
"""The seven dimensions for one capability. Never collapsed."""
known = [c for c in comps if c in arch]
out = {}
# designed: the spec states every one of these, so the question this
# dimension answers is narrower. Does a living doc own the subsystem.
st = cap["state"].lower()
if "no package" in st:
out["designed"] = "spec-only"
elif "no living doc" in st or "no capture client" in st:
out["designed"] = "spec-only"
else:
out["designed"] = "yes"
out["code_present"] = roll([1] * len(known)) if comps else "no"
for i, name in enumerate(DIMS):
out[name] = roll([STATUS_DIMS[arch[c]][i] for c in known])
vs = [verdicts.get(cr["id"], {}).get("verified", "untested")
for cr in cap["criteria"]]
if vs and all(v == "pass" for v in vs):
out["verified"] = "yes"
elif any(v == "pass" for v in vs):
out["verified"] = "partial"
else:
out["verified"] = "no"
return out
def load_flat(path):
"""`key: [a, b]` per line, # comments stripped. domains and implementation."""
if not path.exists():
return {}
out = {}
for line in path.read_text(encoding="utf-8").splitlines():
line = line.split("#", 1)[0].strip()
if not line or ":" not in line:
continue
k, v = line.split(":", 1)
vals = [x.strip() for x in v.strip().strip("[]").split(",") if x.strip()]
out[k.strip()] = vals
return out
def main():
caps, section_notes = parse()
domains = load_flat(DOMAINS)
impl = load_flat(IMPL)
arch = load_arch()
verdicts = load_verdicts()
errs = []
seen = {}
for c in caps:
for cr in c["criteria"]:
if cr["id"] in seen:
errs.append(f"criterion id collision: {cr['id']}")
seen[cr["id"]] = cr["text"]
if not c["criteria"]:
errs.append(f"{c['id']}: no DoD criteria extracted")
if not c["state"]:
errs.append(f"{c['id']}: no State line extracted")
d = domains.get(c["id"])
if domains:
if not d:
errs.append(f"{c['id']}: no domain assigned")
elif len(d) > 2:
errs.append(f"{c['id']}: {len(d)} domains, max is 2")
else:
for x in d:
if x not in DOMAIN_NAMES:
errs.append(f"{c['id']}: unknown domain {x!r}")
cap_ids = {c["id"] for c in caps}
for k in domains:
if k not in cap_ids:
errs.append(f"domains.yaml names unknown capability {k!r}")
# The mapping is the whole basis of the implementation columns. A capability
# missing from it reads as `no` on every dimension, which is indistinguishable
# from a capability nothing carries. Refuse rather than guess which.
if impl:
if not arch:
errs.append("implementation.yaml is present and "
"docs/architecture/maven-architecture.json is not")
for k in impl:
if k not in cap_ids:
errs.append(f"implementation.yaml names unknown capability {k!r}")
for c in caps:
if c["id"] not in impl:
errs.append(f"{c['id']}: no row in implementation.yaml")
for k, comps in impl.items():
for comp in comps:
if arch and comp not in arch:
errs.append(f"{k}: unknown component {comp!r}")
for comp, st in arch.items():
if st not in STATUS_DIMS:
errs.append(f"maven-architecture.json: unknown status {st!r} on {comp}")
# A verdict cites evidence by path, and a path that resolves to nothing is
# worse than no citation: it reads as verified and is not. Section refs are
# checked too, because writing "§ Something" that no heading matches is the
# easy way to make an unsupported claim look sourced.
for cid, v in verdicts.items():
for e in v.get("evidence", []):
path, _, section = e.partition(" § ")
# An evidence string is "<path>[:line] [locator]" or
# "<path> § <heading>". The locator points inside the file
# (a probe id, a readback key) and is not part of the path.
path = path.strip().split()[0].split(":")[0]
f = ROOT / path
if not f.exists():
errs.append(f"{cid}: evidence path does not exist: {path}")
elif section and f.suffix == ".md" and section.strip() not in f.read_text(encoding="utf-8"):
errs.append(f"{cid}: evidence names a section not in {path}: {section}")
for cid, v in verdicts.items():
if cid not in seen:
errs.append(f"verdicts.json scores unknown criterion {cid!r}")
elif v.get("verified") not in VERDICT_WORDS:
errs.append(f"{cid}: verdict {v.get('verified')!r} is not one of {sorted(VERDICT_WORDS)}")
elif v.get("reason") not in REASON_KINDS:
errs.append(f"{cid}: reason {v.get('reason')!r} is not one of the plan's kinds")
elif v["verified"] == "pass" and v["reason"] != "passes":
errs.append(f"{cid}: a pass carries reason {v['reason']!r}")
elif v["verified"] != "pass" and v["reason"] == "passes":
errs.append(f"{cid}: reason 'passes' on a {v['verified']} verdict")
elif v["verified"] == "fail" and v["reason"] == "implementation exists with no runtime proof":
# That reason means nothing was observed. A fail was observed, or it
# is not a fail. Mixing them is how a wrong diagnosis survives.
errs.append(f"{cid}: a fail cannot rest on 'no runtime proof'")
OUT.write_text(emit(caps, section_notes, domains, verdicts, impl, arch), encoding="utf-8")
# The emitter hand-writes YAML, so it can produce something that reads fine
# and does not parse. It did once: evidence came out as a bare list item
# inside a mapping. Parse what was just written.
try:
import yaml
except ImportError:
errs.append("pyyaml absent: the output was written without a parse check")
else:
try:
doc = yaml.safe_load(OUT.read_text(encoding="utf-8"))
except yaml.YAMLError as e:
errs.append(f"the emitted ledger is not valid YAML: {e}")
else:
n = sum(len(c["criteria"]) for c in doc["capabilities"])
if n != len(seen):
errs.append(f"round trip lost criteria: wrote {len(seen)}, read back {n}")
for c in doc["capabilities"]:
if c["scope"] != "v1":
continue
for cr in c["criteria"]:
if cr["verified"] != "untested" and not cr.get("evidence"):
errs.append(f"{cr['id']}: scored {cr['verified']} with no evidence")
print(f"{len(caps)} capabilities, {len(seen)} criteria -> {OUT.relative_to(ROOT)}")
print(f" v1: {sum(1 for c in caps if c['scope'] == 'v1')}, "
f"deferred: {sum(1 for c in caps if c['scope'] == 'deferred')}")
scen = {s["name"]: s["exists"] for c in caps for s in c["scenarios"]["named"]}
ghosts = sorted(n for n, e in scen.items() if not e
and any(s["claimed"] for c in caps for s in c["scenarios"]["named"] if s["name"] == n))
if ghosts:
print(f" named as existing but absent from disk: {', '.join(ghosts)}")
print(f" scenarios named: {len(scen)}, existing: {sum(scen.values())}, "
f"to write: {len(scen) - sum(scen.values())}")
if verdicts:
from collections import Counter
tally = Counter(v["verified"] for v in verdicts.values())
print(" verdicts: " + ", ".join(f"{k} {n}" for k, n in sorted(tally.items())))
if arch:
from collections import Counter as _C
for k in ("code_present", "wired", "configured", "deployed", "reachable"):
t = _C(dimensions(c, impl.get(c["id"], []), arch, verdicts)[k] for c in caps)
print(f" {k}: " + ", ".join(f"{a} {n}" for a, n in sorted(t.items())))
# A capability nothing carries that still scores a pass. Always a
# negative criterion passing by absence. Worth seeing, not an error.
for c in caps:
d_ = dimensions(c, impl.get(c["id"], []), arch, verdicts)
if d_["code_present"] == "no" and d_["verified"] != "no":
print(f" ANOMALY {c['id']}: nothing carries it and it scores "
f"verified={d_['verified']} (a negative criterion passing by absence)")
used = {x for v in impl.values() for x in v}
orphan = sorted(set(arch) - used)
print(f" components serving no capability: {len(orphan)}")
for o in orphan:
print(f" {o} ({arch[o]})")
print(f" no living doc: {sum(1 for c in caps if 'No living doc' in c['state'] or 'no living doc' in c['state'].lower())}")
if errs:
print("\nERRORS:", file=sys.stderr)
for e in errs:
print(" " + e, file=sys.stderr)
return 1
return 0
if __name__ == "__main__":
sys.exit(main())