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: