From 40ec0c0d4bfa83573b858ac94305118b36c6df30 Mon Sep 17 00:00:00 2001 From: claude Date: Wed, 26 Aug 2026 12:37:37 +0400 Subject: [PATCH] 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 --- docs/capabilities/README.md | 7 +- docs/capabilities/build_ledger.py | 144 +++++++- docs/capabilities/implementation.yaml | 88 +++++ docs/capabilities/ledger.yaml | 465 +++++++++++++++++++++++++- 4 files changed, 690 insertions(+), 14 deletions(-) create mode 100644 docs/capabilities/implementation.yaml diff --git a/docs/capabilities/README.md b/docs/capabilities/README.md index addb143..ec4ca3c 100644 --- a/docs/capabilities/README.md +++ b/docs/capabilities/README.md @@ -11,9 +11,10 @@ whole directory is built against. | file | what it is | hand-edited | | --- | --- | --- | -| `ledger.yaml` | the ledger: 51 capabilities, 156 DoD criteria, one verdict per criterion | no | +| `ledger.yaml` | the ledger: 51 capabilities, 156 DoD criteria, one verdict per criterion, seven implementation dimensions per capability | no | | `build_ledger.py` | extracts the ledger from `docs/spec.md` and joins the two inputs | yes, it is the source | -| `domains.yaml` | the domain axis, the one judgment call in the extraction | yes | +| `domains.yaml` | the domain axis, one of the two judgment calls in the extraction | yes | +| `implementation.yaml` | capability to component mapping, the other judgment call | yes | | `verdicts.json` | one verdict per criterion id, produced by scoring a probe run | no, scored | | `probes_field.json` | 25 multi-turn probes: the owner's real week | yes | | `probes_dod.json` | probes derived from the ledger's criteria | no, generated | @@ -24,7 +25,7 @@ whole directory is built against. ## Rebuilding ```sh -python3 docs/capabilities/build_ledger.py # spec.md + domains.yaml + verdicts.json -> ledger.yaml +python3 docs/capabilities/build_ledger.py # spec.md + domains.yaml + implementation.yaml + verdicts.json + maven-architecture.json -> ledger.yaml ``` The generator is also the checker. It fails, loudly and non-zero, on a diff --git a/docs/capabilities/build_ledger.py b/docs/capabilities/build_ledger.py index 631eb43..e632a99 100644 --- a/docs/capabilities/build_ledger.py +++ b/docs/capabilities/build_ledger.py @@ -19,6 +19,8 @@ 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. @@ -225,7 +227,7 @@ def load_verdicts(): return json.loads(VERDICTS.read_text(encoding="utf-8")) -def emit(caps, section_notes, domains, verdicts): +def emit(caps, section_notes, domains, verdicts, impl, arch): L = [] L.append("# Capability ledger, target side.") L.append("#") @@ -233,8 +235,10 @@ def emit(caps, section_notes, domains, verdicts): 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("# No implementation status and no verification status appear here.") - L.append("# Session 1 step 2 writes verdicts against the criterion ids below.") + 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)}") @@ -255,6 +259,16 @@ def emit(caps, section_notes, domains, verdicts): 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"): @@ -289,11 +303,82 @@ def emit(caps, section_notes, domains, verdicts): return "\n".join(L) + "\n" -def load_domains(): - if not DOMAINS.exists(): +# --- 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 DOMAINS.read_text(encoding="utf-8").splitlines(): + for line in path.read_text(encoding="utf-8").splitlines(): line = line.split("#", 1)[0].strip() if not line or ":" not in line: continue @@ -305,7 +390,9 @@ def load_domains(): def main(): caps, section_notes = parse() - domains = load_domains() + domains = load_flat(DOMAINS) + impl = load_flat(IMPL) + arch = load_arch() verdicts = load_verdicts() errs = [] @@ -329,10 +416,32 @@ def main(): 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 {c["id"] for c in caps}: + 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 @@ -366,7 +475,7 @@ def main(): # 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), encoding="utf-8") + 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 @@ -405,6 +514,23 @@ def main(): 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) diff --git a/docs/capabilities/implementation.yaml b/docs/capabilities/implementation.yaml new file mode 100644 index 0000000..277bdc9 --- /dev/null +++ b/docs/capabilities/implementation.yaml @@ -0,0 +1,88 @@ +# Capability -> component mapping. HAND-WRITTEN. This is the judgment call. +# +# Component ids come from docs/architecture/maven-architecture.json, whose +# `status` field was read from code, config and compose and audited against +# them. build_ledger.py derives the six implementation dimensions from those +# statuses and refuses an id that file does not carry. +# +# What is mapped is what CARRIES the capability, never the infrastructure every +# capability shares. core.reactive_handler, core.wiring, core.action_table, +# core.daemon_api and bnd.ipc are deliberately absent: mapping them everywhere +# would give all 51 rows the same status and say nothing. +# +# An empty list means no component carries it. That is the finding, not a hole +# in this file. + +# --- The turn --- +route-an-utterance: [router.cascade, router.stage0, router.heads, router.llm, router.classifier, router.embedder, router.extractor, core.turn_route, core.topics, core.decision_trace, state.decision_ring, state.routing_traces, state.routing_labels] +ask-instead-of-guessing: [core.preroute, state.clarify_store, state.dialogue_sessions] +speak-as-herself: [core.phraser, core.replier, core.action_chat, core.model_seam, svc.llama_server, eval.phrasing] +answer-from-your-own-data: [core.query_chain, core.q.embed, core.q.memory, core.q.factbykey, core.q.notes, core.q.history, core.q.list, core.q.self, core.q.personal, state.list_items] +answer-from-the-world: [core.q.search, core.q.web, core.q.general, core.q.personal, ext.searxng] +read-an-encyclopedia: [core.q.kiwix, ext.kiwix] +weather: [core.q.weather, ext.openmeteo] +see-an-image: [core.vision, state.media_blobs] + +# --- Memory --- +facts: [state.facts, core.action_fact, core.fact_enrichment, core.store_api] +notes: [state.notes, core.action_note] +recall: [core.recall, state.memory_vectors, router.embedder, core.q.memory, core.q.notes] +memory-evaluation: [core.memory_eval] + +# --- Proactive --- +reminders: [state.reminders, core.action_reminder, core.dispatcher, state.delivery_attempts] +interruption-policy: [core.rules, core.dispatcher, core.gatherer, state.presence_state, state.nudges, state.tick_memo] +digest-of-held-nudges: [state.digest_entries, core.tick_loop, core.rules] +morning-routine: [core.morning, core.q.dayplan] +routine-proposals: [core.pattern, core.routines, state.proposed_routines, state.events] +tasks: [state.tasks, core.q.tasks] +rss-and-news: [core.feed_worker, core.q.feeds] + +# --- Reach --- +telegram: [core.sink_telegram, core.telegram_intake, ext.telegram, state.ack_sends] +ntfy: [core.sink_ntfy, ext.ntfy] +voice: [core.voice_server, bnd.voice_tcp, core.sink_voice, proc.mavenclient] +web-ui: [proc.mavweb, bnd.http_web] +desk-notifications: [core.event_bus, proc.mavweb] + +# --- Speech and senses --- +speech-to-text: [core.stt_seam, proc.mavsttd, ext.whispercpp, ext.cw2_stt, bnd.worker] +text-to-speech: [core.tts_seam, proc.mavttsd, ext.piper, bnd.worker] +wake-word: [proc.mavwaked, cfg.systemd, ext.alsa] +hearing: [core.capture, state.media_blobs] +speaker-recognition: [core.speaker] + +# --- The ecosystem --- +nexus: [ext.nexus, core.ecosystem, bnd.http_ecosystem, state.ecosystem_traces] +praxis: [ext.praxis, core.ecosystem, core.praxis_acts, core.q.attention, state.surfaced_items, state.ecosystem_traces] +hexis: [ext.hexis, core.ecosystem, core.ecosystem_hexis_gate, core.action_act, core.risk_policy, state.tools, state.pending_act, state.ecosystem_traces] +smart-home: [ext.homeassistant, core.home_worker, core.q.home] +network-scans: [core.netscan, core.q.network] +# No package, no component. The finding, not an omission. +bluetooth-control: [] +mcps: [core.mcp_worker, ext.vikunja_mcp] + +# --- Operations --- +the-deployed-stack: [cfg.compose, cfg.mavend, proc.mavend, proc.mavweb, proc.mavsttd, proc.mavttsd, proc.mavpoll, proc.mavgpud, ext.netdata, ext.uptimekuma] +encrypted-database: [state.db_file, state.db, state.db_tmpfs, proc.mavseal] +passkey-and-step-up: [state.wrapped_key, state.passkey_file, core.daemon_lock, core.auth_gate, bnd.http_web] +model-swap: [core.modelswap, svc.llama_server] +self-update: [proc.mavupdate] +tests-and-analyzers: [eval.gates, eval.router, eval.phrasing] + +# --- Undesigned in v1 --- +email-triage: [proc.mavmaild, core.mail_intake, state.maildata] +calendar-management: [proc.mavcaldav, core.q.calendar] +web-crawling: [core.crawl_worker, core.q.web] +summaries: [] +# Empty on purpose. core.telegram_intake is Telegram's own inbound channel and +# is mapped to `telegram`. Mapping it here too would make this row read as +# built and deployed when all three of its criteria fail on code missing. +webhooks: [] +cron-jobs: [core.routines, core.tick_loop] +learning-the-style: [] +# state.routing_labels holds owner corrections of a route and is deliberately +# NOT mapped here. It is route learning, not behavioural learning, and mapping +# it would make this row read as partially built when nothing reads it back. +learning-from-mistakes: [] +command-chaining: [] diff --git a/docs/capabilities/ledger.yaml b/docs/capabilities/ledger.yaml index e2a8e6b..93e12ad 100644 --- a/docs/capabilities/ledger.yaml +++ b/docs/capabilities/ledger.yaml @@ -4,8 +4,10 @@ # Do not hand-edit. Domain assignment is the one human input and # lives in docs/capabilities/domains.yaml. # -# No implementation status and no verification status appear here. -# Session 1 step 2 writes verdicts against the criterion ids below. +# Verification is per criterion, from verdicts.json. Implementation is +# per capability, seven dimensions derived from the component statuses +# in docs/architecture/maven-architecture.json through the mapping in +# docs/capabilities/implementation.yaml. Never one boolean. source: docs/spec.md capability_count: 51 @@ -24,6 +26,15 @@ capabilities: domain: [deliberation] state: >- `docs/routing.md`. + implementation: + designed: 'yes' + code_present: 'yes' + wired: 'yes' + configured: 'yes' + deployed: 'yes' + reachable: 'yes' + verified: 'partial' + components: [router.cascade, router.stage0, router.heads, router.llm, router.classifier, router.embedder, router.extractor, core.turn_route, core.topics, core.decision_trace, state.decision_ring, state.routing_traces, state.routing_labels] scenarios: - name: conversation_anaphora exists: true @@ -77,6 +88,15 @@ capabilities: domain: [deliberation, interaction] state: >- `docs/routing.md`, the clarify head and the parked clarify ride. + implementation: + designed: 'yes' + code_present: 'yes' + wired: 'yes' + configured: 'yes' + deployed: 'yes' + reachable: 'yes' + verified: 'partial' + components: [core.preroute, state.clarify_store, state.dialogue_sessions] scenarios: - name: conversation_anaphora exists: true @@ -119,6 +139,15 @@ capabilities: domain: [interaction] state: >- `docs/language.md`. + implementation: + designed: 'yes' + code_present: 'yes' + wired: 'yes' + configured: 'partial' + deployed: 'partial' + reachable: 'partial' + verified: 'no' + components: [core.phraser, core.replier, core.action_chat, core.model_seam, svc.llama_server, eval.phrasing] scenarios: - name: morning_missed exists: true @@ -162,6 +191,15 @@ capabilities: domain: [memory, governance] state: >- `docs/routing.md`, `queryWalk` in `cmd/mavend/actions_query.go`. + implementation: + designed: 'yes' + code_present: 'yes' + wired: 'yes' + configured: 'yes' + deployed: 'yes' + reachable: 'yes' + verified: 'partial' + components: [core.query_chain, core.q.embed, core.q.memory, core.q.factbykey, core.q.notes, core.q.history, core.q.list, core.q.self, core.q.personal, state.list_items] scenarios: - name: assistant_workday exists: true @@ -205,6 +243,15 @@ capabilities: domain: [action, governance] state: >- `docs/world.md`. + implementation: + designed: 'yes' + code_present: 'yes' + wired: 'yes' + configured: 'yes' + deployed: 'yes' + reachable: 'yes' + verified: 'no' + components: [core.q.search, core.q.web, core.q.general, core.q.personal, ext.searxng] scenarios: - name: world_summary_quality exists: false @@ -246,6 +293,15 @@ capabilities: domain: [action] state: >- `docs/world.md`, the Kiwix section. + implementation: + designed: 'yes' + code_present: 'yes' + wired: 'yes' + configured: 'yes' + deployed: 'yes' + reachable: 'yes' + verified: 'no' + components: [core.q.kiwix, ext.kiwix] scenarios: - name: kiwix_language_pick exists: false @@ -287,6 +343,15 @@ capabilities: domain: [action, deliberation] state: >- `internal/weather`. No living doc covers it. + implementation: + designed: 'spec-only' + code_present: 'yes' + wired: 'yes' + configured: 'partial' + deployed: 'partial' + reachable: 'partial' + verified: 'no' + components: [core.q.weather, ext.openmeteo] finding: >- the provider seam, the home city and the clarify path have no written reasoning anywhere. The audit found the capability broken on configuration alone. scenarios: @@ -340,6 +405,15 @@ capabilities: domain: [action, perception] state: >- `internal/vision`, the seam that stores the image and says so. No living doc. + implementation: + designed: 'spec-only' + code_present: 'yes' + wired: 'yes' + configured: 'no' + deployed: 'no' + reachable: 'no' + verified: 'no' + components: [core.vision, state.media_blobs] finding: >- V-667 has the gemma-4 mmproj on the box and no written contract for what a vision call returns. scenarios: @@ -385,6 +459,15 @@ capabilities: domain: [memory] state: >- `internal/store/facts.go`, `cmd/mavend/factenrichment.go`. No living doc. + implementation: + designed: 'spec-only' + code_present: 'yes' + wired: 'yes' + configured: 'yes' + deployed: 'yes' + reachable: 'yes' + verified: 'partial' + components: [state.facts, core.action_fact, core.fact_enrichment, core.store_api] scenarios: - name: morning_missed exists: true @@ -435,6 +518,15 @@ capabilities: domain: [memory] state: >- `internal/memory`. No living doc. + implementation: + designed: 'spec-only' + code_present: 'yes' + wired: 'yes' + configured: 'yes' + deployed: 'yes' + reachable: 'yes' + verified: 'partial' + components: [state.notes, core.action_note] scenarios: - name: note_delete exists: false @@ -476,6 +568,15 @@ capabilities: domain: [memory, governance] state: >- `docs/routing.md` for the query walk and the personal boundary. + implementation: + designed: 'yes' + code_present: 'yes' + wired: 'yes' + configured: 'yes' + deployed: 'yes' + reachable: 'yes' + verified: 'partial' + components: [core.recall, state.memory_vectors, router.embedder, core.q.memory, core.q.notes] scenarios: - name: assistant_workday exists: true @@ -527,6 +628,15 @@ capabilities: domain: [memory, deliberation] state: >- `internal/memeval`. No living doc. + implementation: + designed: 'spec-only' + code_present: 'yes' + wired: 'yes' + configured: 'no' + deployed: 'no' + reachable: 'no' + verified: 'no' + components: [core.memory_eval] finding: >- the evaluator ships, writes notes and cannot speak, and nothing records what its conclusions mean (V-248). scenarios: @@ -561,6 +671,15 @@ capabilities: domain: [attention, interaction] state: >- `internal/store`, `internal/delivery`. No living doc covers the reminder lifecycle. + implementation: + designed: 'spec-only' + code_present: 'yes' + wired: 'yes' + configured: 'yes' + deployed: 'yes' + reachable: 'yes' + verified: 'partial' + components: [state.reminders, core.action_reminder, core.dispatcher, state.delivery_attempts] finding: >- parking, firing, delivery, retry and cancellation are spread across three packages with no written contract. scenarios: @@ -622,6 +741,15 @@ capabilities: domain: [initiative, perception] state: >- `docs/handler-wiring.md` for the dispatch decision. + implementation: + designed: 'yes' + code_present: 'yes' + wired: 'yes' + configured: 'yes' + deployed: 'yes' + reachable: 'yes' + verified: 'no' + components: [core.rules, core.dispatcher, core.gatherer, state.presence_state, state.nudges, state.tick_memo] finding: >- the four presence-and-severity outcomes have never been written down as intended behaviour, only as code (V-281). scenarios: @@ -667,6 +795,15 @@ capabilities: domain: [initiative, attention] state: >- `internal/worker`, the digestion worker. No living doc. + implementation: + designed: 'spec-only' + code_present: 'yes' + wired: 'yes' + configured: 'yes' + deployed: 'yes' + reachable: 'yes' + verified: 'no' + components: [state.digest_entries, core.tick_loop, core.rules] scenarios: - name: evening_degraded exists: true @@ -708,6 +845,15 @@ capabilities: domain: [initiative, interaction] state: >- `internal/morning`, `internal/routine`. No living doc. + implementation: + designed: 'spec-only' + code_present: 'yes' + wired: 'yes' + configured: 'yes' + deployed: 'yes' + reachable: 'yes' + verified: 'no' + components: [core.morning, core.q.dayplan] scenarios: - name: morning_missed exists: true @@ -749,6 +895,15 @@ capabilities: domain: [initiative, memory] state: >- `internal/routine`. No living doc. + implementation: + designed: 'spec-only' + code_present: 'yes' + wired: 'yes' + configured: 'yes' + deployed: 'yes' + reachable: 'yes' + verified: 'no' + components: [core.pattern, core.routines, state.proposed_routines, state.events] finding: >- the proposer reads a hand-written Russian verb list, which the language rules forbid as a route or fact source (V-606). scenarios: @@ -792,6 +947,15 @@ capabilities: domain: [attention, memory] state: >- `internal/tasks`. No living doc. + implementation: + designed: 'spec-only' + code_present: 'yes' + wired: 'yes' + configured: 'yes' + deployed: 'yes' + reachable: 'yes' + verified: 'partial' + components: [state.tasks, core.q.tasks] scenarios: - name: assistant_workday exists: true @@ -835,6 +999,15 @@ capabilities: domain: [action] state: >- `internal/rss`. No living doc. + implementation: + designed: 'spec-only' + code_present: 'yes' + wired: 'yes' + configured: 'yes' + deployed: 'yes' + reachable: 'yes' + verified: 'partial' + components: [core.feed_worker, core.q.feeds] scenarios: - name: morning_missed exists: true @@ -876,6 +1049,15 @@ capabilities: domain: [interaction] state: >- `docs/deployment.md`, `internal/delivery/telegramsink`. + implementation: + designed: 'yes' + code_present: 'yes' + wired: 'yes' + configured: 'yes' + deployed: 'yes' + reachable: 'yes' + verified: 'partial' + components: [core.sink_telegram, core.telegram_intake, ext.telegram, state.ack_sends] scenarios: - name: evening_degraded exists: true @@ -917,6 +1099,15 @@ capabilities: domain: [interaction] state: >- `internal/delivery/ntfysink`, disabled in the committed config. + implementation: + designed: 'yes' + code_present: 'yes' + wired: 'yes' + configured: 'no' + deployed: 'no' + reachable: 'no' + verified: 'no' + components: [core.sink_ntfy, ext.ntfy] scenarios: - name: ntfy_403 exists: false @@ -950,6 +1141,15 @@ capabilities: domain: [interaction, governance] state: >- `docs/protocol.md` for the wire, `internal/delivery/voicesink` for the sink. + implementation: + designed: 'yes' + code_present: 'yes' + wired: 'yes' + configured: 'yes' + deployed: 'yes' + reachable: 'yes' + verified: 'partial' + components: [core.voice_server, bnd.voice_tcp, core.sink_voice, proc.mavenclient] finding: >- the wire is documented and the listener is not. Nothing describes what holds a live voice session open. scenarios: @@ -994,6 +1194,15 @@ capabilities: domain: [interaction, governance] state: >- `docs/deployment.md`. + implementation: + designed: 'yes' + code_present: 'yes' + wired: 'yes' + configured: 'yes' + deployed: 'yes' + reachable: 'yes' + verified: 'partial' + components: [proc.mavweb, bnd.http_web] scenarios: scenario_note: >- covered by `cmd/mavweb` tests, not by a scenario. @@ -1033,6 +1242,15 @@ capabilities: domain: [perception, interaction] state: >- `cmd/mavweb/ambient.go`, `internal/event`. No living doc. + implementation: + designed: 'spec-only' + code_present: 'yes' + wired: 'yes' + configured: 'yes' + deployed: 'yes' + reachable: 'yes' + verified: 'no' + components: [core.event_bus, proc.mavweb] finding: >- the inbound direction exists as the `ambient:notif` source and the outbound direction does not exist at all. Which one the owner means is an open product decision. scenarios: @@ -1068,6 +1286,15 @@ capabilities: domain: [interaction, perception] state: >- `docs/offload.md`, `docs/deployment.md`. + implementation: + designed: 'yes' + code_present: 'yes' + wired: 'yes' + configured: 'yes' + deployed: 'yes' + reachable: 'yes' + verified: 'no' + components: [core.stt_seam, proc.mavsttd, ext.whispercpp, ext.cw2_stt, bnd.worker] scenarios: scenario_note: >- covered by `docs/evals/2026-08-09-crisperwhisper2-russian-wer.md`. @@ -1108,6 +1335,15 @@ capabilities: domain: [interaction] state: >- `docs/offload.md`, `docs/deployment.md`. + implementation: + designed: 'yes' + code_present: 'yes' + wired: 'yes' + configured: 'yes' + deployed: 'yes' + reachable: 'yes' + verified: 'no' + components: [core.tts_seam, proc.mavttsd, ext.piper, bnd.worker] scenarios: - name: tts_normalisation exists: false @@ -1142,6 +1378,15 @@ capabilities: domain: [perception, interaction] state: >- `docs/deployment.md`, `mavwaked` under systemd on workpc. + implementation: + designed: 'yes' + code_present: 'yes' + wired: 'yes' + configured: 'yes' + deployed: 'yes' + reachable: 'yes' + verified: 'no' + components: [proc.mavwaked, cfg.systemd, ext.alsa] scenarios: - name: voice_push exists: false @@ -1175,6 +1420,15 @@ capabilities: domain: [perception, interaction] state: >- `internal/capture`, `internal/audio`. No capture client ships (V-514). + implementation: + designed: 'spec-only' + code_present: 'yes' + wired: 'yes' + configured: 'no' + deployed: 'no' + reachable: 'no' + verified: 'no' + components: [core.capture, state.media_blobs] scenarios: - name: voice_push exists: false @@ -1208,6 +1462,15 @@ capabilities: domain: [perception, governance] state: >- `internal/speaker`. No living doc (V-255). + implementation: + designed: 'spec-only' + code_present: 'yes' + wired: 'yes' + configured: 'no' + deployed: 'no' + reachable: 'no' + verified: 'no' + components: [core.speaker] deferred_note: >- **Deferred past v1**, owner's call 2026-08-15. See `docs/roadmap.md`. scenarios: @@ -1234,6 +1497,15 @@ capabilities: domain: [deliberation, governance] state: >- `docs/ecosystem.md`. + implementation: + designed: 'yes' + code_present: 'yes' + wired: 'yes' + configured: 'yes' + deployed: 'yes' + reachable: 'yes' + verified: 'no' + components: [ext.nexus, core.ecosystem, bnd.http_ecosystem, state.ecosystem_traces] scenarios: - name: act_degraded exists: true @@ -1285,6 +1557,15 @@ capabilities: domain: [attention, governance] state: >- `docs/ecosystem.md`. + implementation: + designed: 'yes' + code_present: 'yes' + wired: 'yes' + configured: 'yes' + deployed: 'yes' + reachable: 'yes' + verified: 'no' + components: [ext.praxis, core.ecosystem, core.praxis_acts, core.q.attention, state.surfaced_items, state.ecosystem_traces] scenarios: - name: morning_missed exists: true @@ -1338,6 +1619,15 @@ capabilities: domain: [action, governance] state: >- `docs/ecosystem.md`. + implementation: + designed: 'yes' + code_present: 'yes' + wired: 'yes' + configured: 'yes' + deployed: 'yes' + reachable: 'yes' + verified: 'no' + components: [ext.hexis, core.ecosystem, core.ecosystem_hexis_gate, core.action_act, core.risk_policy, state.tools, state.pending_act, state.ecosystem_traces] scenarios: - name: act_degraded exists: true @@ -1389,6 +1679,15 @@ capabilities: domain: [action, governance] state: >- `internal/smarthome`, disabled in config (V-256). + implementation: + designed: 'yes' + code_present: 'yes' + wired: 'yes' + configured: 'partial' + deployed: 'partial' + reachable: 'partial' + verified: 'no' + components: [ext.homeassistant, core.home_worker, core.q.home] deferred_note: >- **Deferred past v1**, owner's call 2026-08-15. See `docs/roadmap.md`. scenarios: @@ -1415,6 +1714,15 @@ capabilities: domain: [action] state: >- `internal/netscan`, `internal/netaddr`. No living doc. + implementation: + designed: 'spec-only' + code_present: 'yes' + wired: 'yes' + configured: 'yes' + deployed: 'yes' + reachable: 'yes' + verified: 'partial' + components: [core.netscan, core.q.network] scenarios: - name: netscan_query exists: false @@ -1456,6 +1764,15 @@ capabilities: domain: [action] state: >- no package. + implementation: + designed: 'spec-only' + code_present: 'no' + wired: 'no' + configured: 'no' + deployed: 'no' + reachable: 'no' + verified: 'no' + components: [] finding: >- nothing exists, and the box has no bluez (V-257). This is the only v1 item blocked on the host rather than on code. deferred_note: >- @@ -1484,6 +1801,15 @@ capabilities: domain: [action, governance] state: >- `internal/mcp`. No living doc. + implementation: + designed: 'spec-only' + code_present: 'yes' + wired: 'yes' + configured: 'no' + deployed: 'no' + reachable: 'no' + verified: 'no' + components: [core.mcp_worker, ext.vikunja_mcp] finding: >- the allowlist, the stdio and http transports and the webfetch door all exist. Nothing records which servers may run, or why. scenarios: @@ -1527,6 +1853,15 @@ capabilities: domain: [operations] state: >- `docs/deployment.md`. + implementation: + designed: 'yes' + code_present: 'yes' + wired: 'yes' + configured: 'yes' + deployed: 'yes' + reachable: 'yes' + verified: 'no' + components: [cfg.compose, cfg.mavend, proc.mavend, proc.mavweb, proc.mavsttd, proc.mavttsd, proc.mavpoll, proc.mavgpud, ext.netdata, ext.uptimekuma] scenarios: scenario_note: >- none. This is checked by `docker compose ps` and the startup log. @@ -1567,6 +1902,15 @@ capabilities: domain: [operations, governance] state: >- `docs/deployment.md`, `docs/caveats/storage.md`. + implementation: + designed: 'yes' + code_present: 'yes' + wired: 'yes' + configured: 'yes' + deployed: 'yes' + reachable: 'yes' + verified: 'partial' + components: [state.db_file, state.db, state.db_tmpfs, proc.mavseal] scenarios: scenario_note: >- none. Checked by `mavseal` and the startup log. @@ -1609,6 +1953,15 @@ capabilities: domain: [governance, operations] state: >- `docs/caveats/security.md`, `internal/webauthn`, `internal/auth`. + implementation: + designed: 'yes' + code_present: 'yes' + wired: 'yes' + configured: 'yes' + deployed: 'yes' + reachable: 'yes' + verified: 'no' + components: [state.wrapped_key, state.passkey_file, core.daemon_lock, core.auth_gate, bnd.http_web] scenarios: - name: stepup_gate exists: false @@ -1650,6 +2003,15 @@ capabilities: domain: [operations] state: >- `docs/deployment.md` (V-250). + implementation: + designed: 'yes' + code_present: 'yes' + wired: 'yes' + configured: 'partial' + deployed: 'partial' + reachable: 'partial' + verified: 'no' + components: [core.modelswap, svc.llama_server] deferred_note: >- **Deferred past v1**, owner's call 2026-08-15. See `docs/roadmap.md`. scenarios: @@ -1674,6 +2036,15 @@ capabilities: domain: [operations] state: >- `cmd/mavupdate`, `internal/update`. Blocked at step 3: it cannot reach the containerized socket (V-477). + implementation: + designed: 'yes' + code_present: 'yes' + wired: 'yes' + configured: 'yes' + deployed: 'yes' + reachable: 'yes' + verified: 'no' + components: [proc.mavupdate] deferred_note: >- **Deferred past v1**, owner's call 2026-08-15. See `docs/roadmap.md`. scenarios: @@ -1698,6 +2069,15 @@ capabilities: domain: [operations] state: >- `docs/qa.md`, `docs/workflow.md`. + implementation: + designed: 'yes' + code_present: 'yes' + wired: 'yes' + configured: 'yes' + deployed: 'yes' + reachable: 'yes' + verified: 'partial' + components: [eval.gates, eval.router, eval.phrasing] scenarios: scenario_note: >- none. This gate is the suite itself. @@ -1739,6 +2119,15 @@ capabilities: domain: [action, governance] state: >- `internal/email`, `cmd/mavmaild`. Built and **not in `docker-compose.yml`**. + implementation: + designed: 'yes' + code_present: 'yes' + wired: 'yes' + configured: 'partial' + deployed: 'no' + reachable: 'no' + verified: 'partial' + components: [proc.mavmaild, core.mail_intake, state.maildata] finding: >- the product decision comes first. What she does with his mail is undecided, and deploying the daemon before deciding writes the decision by accident. scenarios: @@ -1793,6 +2182,15 @@ capabilities: domain: [action, deliberation] state: >- `internal/calendar`, `cmd/mavcaldav`. Built and **not in `docker-compose.yml`**. Same product decision as email. + implementation: + designed: 'yes' + code_present: 'yes' + wired: 'yes' + configured: 'yes' + deployed: 'partial' + reachable: 'partial' + verified: 'partial' + components: [proc.mavcaldav, core.q.calendar] scenarios: - name: calendar_create exists: false @@ -1843,6 +2241,15 @@ capabilities: domain: [action, governance] state: >- `internal/crawl` with `robots.go` and `watch.go`, `internal/webfetch`. No living doc. + implementation: + designed: 'spec-only' + code_present: 'yes' + wired: 'yes' + configured: 'yes' + deployed: 'yes' + reachable: 'yes' + verified: 'no' + components: [core.crawl_worker, core.q.web] finding: >- politeness and robots are implemented. The scheduling policy is not written anywhere. scenarios: @@ -1899,6 +2306,15 @@ capabilities: domain: [action, governance] state: >- no package. + implementation: + designed: 'spec-only' + code_present: 'no' + wired: 'no' + configured: 'no' + deployed: 'no' + reachable: 'no' + verified: 'no' + components: [] finding: >- summarisation exists inside the world chain and inside digestion, and nothing owns it as a capability he can ask for. scenarios: @@ -1942,6 +2358,15 @@ capabilities: domain: [interaction, governance] state: >- only `internal/delivery/telegramsink/intake.go`, which is Telegram's own inbound webhook. + implementation: + designed: 'yes' + code_present: 'no' + wired: 'no' + configured: 'no' + deployed: 'no' + reachable: 'no' + verified: 'no' + components: [] finding: >- there is no general webhook capability in either direction, and no doc says which direction is wanted. scenarios: @@ -1985,6 +2410,15 @@ capabilities: domain: [action, governance] state: >- `internal/routine`, `cmd/mavend/tick_routines.go`. + implementation: + designed: 'yes' + code_present: 'yes' + wired: 'yes' + configured: 'yes' + deployed: 'yes' + reachable: 'yes' + verified: 'no' + components: [core.routines, core.tick_loop] finding: >- routines carry a `Cron` and are a separate mechanism from reminders. Whether "cron jobs" means user-defined scheduled acts or the existing routines is undecided. scenarios: @@ -2044,6 +2478,15 @@ capabilities: domain: [memory, interaction] state: >- no package. + implementation: + designed: 'spec-only' + code_present: 'no' + wired: 'no' + configured: 'no' + deployed: 'no' + reachable: 'no' + verified: 'partial' + components: [] finding: >- nothing exists beyond `internal/phraser/eval/checks.go`, which scores style and does not learn it. Learning means behavioral, not weights: stored outcomes, no adapter, no training set. scenarios: @@ -2087,6 +2530,15 @@ capabilities: domain: [memory, deliberation] state: >- no package. Same behavioral rule as above. + implementation: + designed: 'spec-only' + code_present: 'no' + wired: 'no' + configured: 'no' + deployed: 'no' + reachable: 'no' + verified: 'no' + components: [] scenarios: - name: learn_from_dismissal exists: false @@ -2137,6 +2589,15 @@ capabilities: domain: [deliberation, governance] state: >- no package. The `chain` in `internal/router` is the world chain and the source chain, not command chaining. + implementation: + designed: 'spec-only' + code_present: 'no' + wired: 'no' + configured: 'no' + deployed: 'no' + reachable: 'no' + verified: 'no' + components: [] finding: >- nothing exists. scenarios: