diff --git a/docs/capabilities/README.md b/docs/capabilities/README.md index e47d0e4..ca2af2b 100644 --- a/docs/capabilities/README.md +++ b/docs/capabilities/README.md @@ -20,6 +20,8 @@ whole directory is built against. | `probes_dod.json` | probes derived from the ledger's criteria | no, generated | | `run_probes.py` | drives a probe file through the deployed stack | yes | | `store_counts.py` | row counts per store, over IPC | yes | +| `invariants.md` | the twelve cross-cutting rules the 51 capabilities imply | yes | +| `gaps.md` | eight gap classes and the one ranked priority list | yes, except classes 1-4 | | `out/` | raw probe output, one JSON object per line | no | ## Rebuilding diff --git a/docs/capabilities/build_ledger.py b/docs/capabilities/build_ledger.py index e632a99..8cf5e6d 100644 --- a/docs/capabilities/build_ledger.py +++ b/docs/capabilities/build_ledger.py @@ -263,7 +263,7 @@ def emit(caps, section_notes, domains, verdicts, impl, arch): dims = dimensions(c, impl.get(c["id"], []), arch, verdicts) L.append(" implementation:") for k in ("designed", "code_present", "wired", "configured", - "deployed", "reachable", "verified"): + "deployed", "reachable", "verified", "gap_class"): # 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}") @@ -364,6 +364,18 @@ def dimensions(cap, comps, arch, verdicts): vs = [verdicts.get(cr["id"], {}).get("verified", "untested") for cr in cap["criteria"]] + # The gap class, for docs/capabilities/gaps.md. Order matters: nothing built + # outranks nothing reachable, which outranks something reachable and wrong. + if out["code_present"] == "no": + out["gap_class"] = "capability missing" + elif out["reachable"] != "yes": + out["gap_class"] = "capability exists but unreachable" + elif any(v == "fail" for v in vs): + out["gap_class"] = "capability partial" + elif all(v == "pass" for v in vs): + out["gap_class"] = "none" + else: + out["gap_class"] = "capability exists but unverified" if vs and all(v == "pass" for v in vs): out["verified"] = "yes" elif any(v == "pass" for v in vs): @@ -526,6 +538,9 @@ def main(): 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)") + t = _C(dimensions(c, impl.get(c["id"], []), arch, verdicts)["gap_class"] + for c in caps if c["scope"] == "v1") + print(" v1 gap classes: " + ", ".join(f"{a} {n}" for a, n in sorted(t.items()))) used = {x for v in impl.values() for x in v} orphan = sorted(set(arch) - used) print(f" components serving no capability: {len(orphan)}") diff --git a/docs/capabilities/gaps.md b/docs/capabilities/gaps.md new file mode 100644 index 0000000..58a69e3 --- /dev/null +++ b/docs/capabilities/gaps.md @@ -0,0 +1,241 @@ +# Gaps: what is missing, and what the architecture does about it + +Hand-written, except the four capability classes, which are derived. + +This file compares responsibilities. It never compares package names. A package +existing is not a capability, and a capability can be spread over six packages +and still be missing. + +## Where each class comes from + +Classes 1 through 4 are the `gap_class` field in `docs/capabilities/ledger.yaml`, +derived from the seven implementation dimensions and the criteria verdicts. +Rebuild them: + +```sh +python3 docs/capabilities/build_ledger.py +``` + +Classes 5 through 8 are read from `docs/architecture/findings.md` and +`docs/capabilities/invariants.md`. **Every entry names the capability or +invariant it affects.** An entry affecting neither is marked non-blocking +cleanup, in those words, and it is the whole of class 8. + +Counts are over the 46 v1 capabilities. The 5 deferred ones are excluded. + +--- + +## 1. Capability missing (5) + +Nothing carries it. `code_present: no`. + +| capability | criteria | note | +| --- | --- | --- | +| `summaries` | 0 pass, 3 fail | no package. Wanted by `email-triage`, `web-crawling`, `hearing` and `rss-and-news`, each of which would consume it | +| `webhooks` | 0 pass, 3 fail | no package. Telegram's own inbound channel is mapped to `telegram`, not here | +| `command-chaining` | 0 pass, 3 fail | no package. The `chain` in `internal/router` is the world chain and the source chain | +| `learning-the-style` | 1 pass, 2 fail | the pass is a negative criterion satisfied by absence. The build reports it as an anomaly | +| `learning-from-mistakes` | 0 pass, 2 fail | no package | + +The last two are invariant 10, and it is `unresolved`. Whether behavioural +learning is wanted is a product question, so these two are not automatically +work. + +## 2. Capability partial (21) + +Reachable, and at least one criterion was observed failing. This is the class +that matters most, because a user can get to all 21 today and 21 misbehave. + +`route-an-utterance`, `ask-instead-of-guessing`, `answer-from-your-own-data`, +`answer-from-the-world`, `read-an-encyclopedia`, `facts`, `notes`, `recall`, +`reminders`, `voice`, `web-ui`, `desk-notifications`, `wake-word`, `nexus`, +`praxis`, `the-deployed-stack`, `encrypted-database`, `passkey-and-step-up`, +`tests-and-analyzers`, `web-crawling`, `cron-jobs`. + +`wake-word` is the sharpest: reachable on every dimension and 0 of 2 criteria +pass. + +## 3. Capability exists but unreachable (9) + +Built, and the deployed configuration does not reach it. + +| capability | why | class of fix | +| --- | --- | --- | +| `speak-as-herself` | `reachable: partial` on `core.model_seam` | configuration | +| `weather` | no `weather` key in the deployed `voice` block | configuration | +| `see-an-image` | no media block | configuration | +| `memory-evaluation` | worker is `configured-off` | configuration | +| `ntfy` | present in the config and disabled there | configuration | +| `hearing` | `capture.enabled` false and no capture client ships (V-514) | configuration and code | +| `mcps` | no MCP server configured, and V-478 blocks the one candidate | deployment | +| `email-triage` | `mavmaild` is not in `docker-compose.yml` | deployment | +| `calendar-management` | `mavcaldav` is not in `docker-compose.yml` | deployment | + +None of these nine is a code defect. Seven are one config block and two are one +compose entry. `docs/spec.md` says this about the audit's four and it still +holds for these nine. + +## 4. Capability exists but unverified (11) + +Reachable, nothing observed failing, and not all criteria pass. These are +measurement gaps, not defects. + +`interruption-policy`, `digest-of-held-nudges`, `morning-routine`, +`routine-proposals`, `tasks`, `rss-and-news`, `telegram`, `speech-to-text`, +`text-to-speech`, `hexis`, `network-scans`. + +Four of them, the whole Proactive cluster, are untested on every criterion, +because a proactive behaviour cannot be probed by sending an utterance. That is +the shape of the gap and it needs a different harness, not more probes. + +--- + +## 5. Duplicated mechanism (6) + +| what | affects | owned? | +| --- | --- | --- | +| Two independent arbitrations decide one turn: seven intents, then twenty-two ordered query sources (`findings.md` 2.1) | invariant 11, `route-an-utterance`, `answer-from-your-own-data` | no | +| A third arbitration runs before both: eleven stateful pre-emptors in the pre-route ladder (`findings.md` 2.2) | invariant 11, invariant 6, `ask-instead-of-guessing` | no | +| Two tier systems. `internal/auth` does not bind the turn path, `internal/tool` is not keyed on the reach (`findings.md` 6.3, 6.3b) | invariant 8, `hexis`, `passkey-and-step-up` | no | +| Two representations of reach, both ignored (`findings.md` 6.3) | invariant 8, `voice` | no | +| Two digest mechanisms with the same word in the name, flushed six lines apart (`findings.md` 2.4) | invariant 5, `digest-of-held-nudges` | no | +| Restraint decided twice: the gate decides whether a rule emits, delivery decides where it lands (`findings.md` 2.3) | `interruption-policy` | **yes**, argued in `channel.go` | + +The last row is duplication that is owned. It is listed so it is not +rediscovered as a defect. + +## 6. Missing shared mechanism (6) + +| what is missing | affects | +| --- | --- | +| A single point that decides whether this origin may perform this effect with this evidence. `origin × effect × evidence → permit` is the target and nothing computes it | invariant 8, `hexis`, `praxis`, `voice`, `passkey-and-step-up` | +| An owner for a key namespace. `facts` has nine writers, `notes` six, `tools` three unrelated proposers (`findings.md` 1.1, 1.2, 1.3) | invariant 2, `facts`, `notes`, `recall` | +| A comparable unit of evidence, so claimants can answer "is this more mine than yours?". `internal/claim` is that unit, written, tested and called by nothing (`findings.md` 6.1) | invariant 11, `command-chaining` | +| A conversation that spans reaches. `mavweb` instead hardcodes one conversation id for the whole web reach | invariant 1, `web-ui`, `voice`, `telegram` | +| A stated rule for what survives a restart. Six stores, six independent choices, two of them argued | invariant 12, `ask-instead-of-guessing` | +| A summariser. Four capabilities would consume one and none exists | `summaries`, `email-triage`, `web-crawling`, `hearing` | + +## 7. Current architecture conflicts with target behavior (10) + +The class where the code works as written and the written thing is not what the +spec asks for. + +| conflict | affects | +| --- | --- | +| Four silent degradations stack on one turn, and `docs/spec.md` writes every v1 DoD at "honest" (`findings.md` 8.1) | invariant 7, `answer-from-the-world`, `speech-to-text`, `route-an-utterance`, `speak-as-herself` | +| `praxisItemAction.handle` calls straight through: acknowledge, resolve, ignore and pin run on first hearing with no tier and no confirm turn (`findings.md` 6.3c, `cmd/mavend/ecosystem_acts.go:158`) | invariant 8, `praxis` | +| `weather` is a live query source with `guesses: true` and the deployed config selects no provider, so it can claim a turn and answer from a stub (`findings.md` 11.3) | invariant 7, `weather` | +| `loop.State.CalendarBusy` reads facts `mavcaldav` never writes, so the do-not-nag-mid-meeting suppressor is permanently false (`findings.md` 8.2) | invariant 3, `interruption-policy`, `calendar-management` | +| Recurring reminders have a column, an IPC parameter and no caller. `actionReminder` passes `""` (`findings.md` 8.3) | `reminders`, `cron-jobs` | +| The clarify store is not persisted and the expired-clarify notice reads the store that is gone (`findings.md` 7.5) | invariant 6, invariant 12, `ask-instead-of-guessing` | +| `Claim.Coverage` returns 1.0 for a claim that extracted nothing (`findings.md` 6.3d) | invariant 11. Latent: it corrupts the fix for class 6 row 3 before that fix ships | +| The act executor runs inside the key holder, and the process boundary is not one of the controls (`findings.md` 5.4) | invariant 8, `hexis`, `encrypted-database` | +| The voice wire's whole security argument is external: loopback publish plus an ssh tunnel, so one compose edit removes it (`findings.md` 5.5) | invariant 8, `voice` | +| `make test` is green with the four `TestONNX*` measurements silently skipped, because the recipe does not set `MAVEN_ONNX_LIB` | `tests-and-analyzers`, `recall`. It is why the predecessor audit had a green suite and 22 dead capabilities | + +## 8. Architecture concern with no current product impact (11) + +**Every row here is non-blocking cleanup.** None names a capability or an +invariant, which is the test for belonging in this class rather than in 5, 6 or +7. + +- `reactiveHandler` has 34 fields (`findings.md` 4.1). +- `runTurn` is one function with eleven early returns (4.2). +- `tick` runs thirteen jobs in one function (4.3). +- `wireVoice` is one constructor for seventeen subsystems (4.4). +- `mavsttd` and `mavttsd` are separate processes at a scale that does not need it (5.1). +- Three IPC connections from one process (5.2). +- A construction cycle between the API layer and the turn layer (3.1). +- The handler holds the raw store beside the mediated one (3.2). +- `queryDayPlan` reads the proactive scheduler, the single call across that line (3.4). +- `internal/modes` is imported by nothing outside itself (6.2). +- The daemon is wired twice, in two places (8.6). + +Two entries were considered for this class and moved out. `queryNetwork` +triggering a live LAN scan inside a read path (6.4) affects `network-scans`, +whose two remaining criteria are `unknown`. `actionFact` re-routing into the +query chain (6.5) affects `facts` and `route-an-utterance`, which is where the +"меня зовут Ками" misroute lives. + +--- + +# Priority + +One list. The rank is the plan's, and it is about impact today, not about how +ugly the code is. An unwired or unreachable future defect never outranks a live +user-visible failure because its architecture is offensive. + +## 1. Prevents intended everyday use today + +1. **`speak-as-herself` fails all three criteria.** The deployed resident model, + `maven-instruct-b2-Q4_K_XL`, produces Russian sentences that no longer hold + together, and the phrasing checks that would catch it run in the eval and not + on the outbound path. Everything that asks the model to write a sentence + inherits this. Formal `вас` and `вы` reached the wire while `CheckFeminine` + passed. +2. **His own name is not stored as a fact.** "меня зовут Ками" routes to chat, + so nothing is written, and "что ты помнишь обо мне?" routes to chat too. + Two of the seven audit probes, still broken and now broken differently. +3. **`wake-word` fails both criteria while reachable on every dimension.** Voice + is the spine of v1 and the always-on half of it does not work. +4. **Nine capabilities are one config block or one compose entry from + reachable.** `weather`, `ntfy`, `see-an-image`, `memory-evaluation`, + `email-triage`, `calendar-management` and `mcps` are the cheap ones. This is + the highest ratio of capability to work in the whole list. + +## 2. Makes existing behavior incorrect or unreliable + +5. **The Praxis lifecycle path has no gate.** Four remote mutations run on first + hearing. This is live today and needs no new wiring to matter. +6. **`weather` answers from a stub and is allowed to claim the turn.** A source + marked `guesses: true` with no provider is worse than a named gap. +7. **The busy suppressor is permanently false.** Every interruption decision + that should have deferred to a meeting fails open. +8. **Four silent degradations stack**, and nothing in a reply distinguishes the + worst case from the best. Invariant 7 has no written boundary between "only + better" and "cannot do the job". +9. **A parked clarify survived five consecutive turns** and was released by a + path other than `отмена`. Invariant 6 says nobody owns closing it. + +## 3. Blocks multiple capabilities + +10. **No single authorization point.** Invariant 8, `unresolved`, and the third + of the three questions the freeze was called to answer. It blocks `hexis`, + `praxis`, `voice` and `passkey-and-step-up`, and it is the one property + nobody can currently state. +11. **No owner for a key namespace.** A fetch watermark and a tuning parameter + live in the table recall embeds and `queryFactByKey` reads back as an + answer. +12. **No comparable unit of evidence.** Three ordered lists decide one turn. + `command-chaining` cannot be built on top of them, and `internal/claim` + carries a live defect before it is wired. +13. **No summariser.** Four capabilities would consume one. + +## 4. Prevents verification + +14. **`make test` is green with four measurements skipped.** The recipe does not + set `MAVEN_ONNX_LIB`. This is the exact trap `CLAUDE.md` describes, and the + baseline walked into it while measuring whether other things had. +15. **The whole Proactive cluster is untested on every criterion.** A proactive + behaviour cannot be probed by sending an utterance. It needs a clock-driving + harness, not more probes. +16. **26 of 31 named scenarios do not exist on disk.** Only 5 of 51 spec entries + cite a scenario that is there (`findings.md` 9.5). +17. **`POST /api/ptt` was called unreachable in an earlier draft and is not.** + Four speech criteria were filed `deployment missing` when the deployment is + present and the probe was never written. + +## 5. Architectural cleanup with no present user impact + +18. Everything in class 8, in any order. None of it blocks a capability or an + invariant, and that is why it is last. + +--- + +## What this file does not do + +It does not schedule. `docs/roadmap.md` orders the work and this file feeds it. + +It does not decide the four `unresolved` invariants. Authority and confirmation, +learning from outcomes, capability composition and the shelf life of a held +nudge are the owner's, and items 10, 12 and 13 above stall on them. diff --git a/docs/capabilities/ledger.yaml b/docs/capabilities/ledger.yaml index 93e12ad..3d6e215 100644 --- a/docs/capabilities/ledger.yaml +++ b/docs/capabilities/ledger.yaml @@ -34,6 +34,7 @@ capabilities: deployed: 'yes' reachable: 'yes' verified: 'partial' + gap_class: 'capability 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 @@ -96,6 +97,7 @@ capabilities: deployed: 'yes' reachable: 'yes' verified: 'partial' + gap_class: 'capability partial' components: [core.preroute, state.clarify_store, state.dialogue_sessions] scenarios: - name: conversation_anaphora @@ -147,6 +149,7 @@ capabilities: deployed: 'partial' reachable: 'partial' verified: 'no' + gap_class: 'capability exists but unreachable' components: [core.phraser, core.replier, core.action_chat, core.model_seam, svc.llama_server, eval.phrasing] scenarios: - name: morning_missed @@ -199,6 +202,7 @@ capabilities: deployed: 'yes' reachable: 'yes' verified: 'partial' + gap_class: 'capability 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 @@ -251,6 +255,7 @@ capabilities: deployed: 'yes' reachable: 'yes' verified: 'no' + gap_class: 'capability partial' components: [core.q.search, core.q.web, core.q.general, core.q.personal, ext.searxng] scenarios: - name: world_summary_quality @@ -301,6 +306,7 @@ capabilities: deployed: 'yes' reachable: 'yes' verified: 'no' + gap_class: 'capability partial' components: [core.q.kiwix, ext.kiwix] scenarios: - name: kiwix_language_pick @@ -351,6 +357,7 @@ capabilities: deployed: 'partial' reachable: 'partial' verified: 'no' + gap_class: 'capability exists but unreachable' 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. @@ -413,6 +420,7 @@ capabilities: deployed: 'no' reachable: 'no' verified: 'no' + gap_class: 'capability exists but unreachable' 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. @@ -467,6 +475,7 @@ capabilities: deployed: 'yes' reachable: 'yes' verified: 'partial' + gap_class: 'capability partial' components: [state.facts, core.action_fact, core.fact_enrichment, core.store_api] scenarios: - name: morning_missed @@ -526,6 +535,7 @@ capabilities: deployed: 'yes' reachable: 'yes' verified: 'partial' + gap_class: 'capability partial' components: [state.notes, core.action_note] scenarios: - name: note_delete @@ -576,6 +586,7 @@ capabilities: deployed: 'yes' reachable: 'yes' verified: 'partial' + gap_class: 'capability partial' components: [core.recall, state.memory_vectors, router.embedder, core.q.memory, core.q.notes] scenarios: - name: assistant_workday @@ -636,6 +647,7 @@ capabilities: deployed: 'no' reachable: 'no' verified: 'no' + gap_class: 'capability exists but unreachable' components: [core.memory_eval] finding: >- the evaluator ships, writes notes and cannot speak, and nothing records what its conclusions mean (V-248). @@ -679,6 +691,7 @@ capabilities: deployed: 'yes' reachable: 'yes' verified: 'partial' + gap_class: 'capability 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. @@ -749,6 +762,7 @@ capabilities: deployed: 'yes' reachable: 'yes' verified: 'no' + gap_class: 'capability exists but unverified' 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). @@ -803,6 +817,7 @@ capabilities: deployed: 'yes' reachable: 'yes' verified: 'no' + gap_class: 'capability exists but unverified' components: [state.digest_entries, core.tick_loop, core.rules] scenarios: - name: evening_degraded @@ -853,6 +868,7 @@ capabilities: deployed: 'yes' reachable: 'yes' verified: 'no' + gap_class: 'capability exists but unverified' components: [core.morning, core.q.dayplan] scenarios: - name: morning_missed @@ -903,6 +919,7 @@ capabilities: deployed: 'yes' reachable: 'yes' verified: 'no' + gap_class: 'capability exists but unverified' 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). @@ -955,6 +972,7 @@ capabilities: deployed: 'yes' reachable: 'yes' verified: 'partial' + gap_class: 'capability exists but unverified' components: [state.tasks, core.q.tasks] scenarios: - name: assistant_workday @@ -1007,6 +1025,7 @@ capabilities: deployed: 'yes' reachable: 'yes' verified: 'partial' + gap_class: 'capability exists but unverified' components: [core.feed_worker, core.q.feeds] scenarios: - name: morning_missed @@ -1057,6 +1076,7 @@ capabilities: deployed: 'yes' reachable: 'yes' verified: 'partial' + gap_class: 'capability exists but unverified' components: [core.sink_telegram, core.telegram_intake, ext.telegram, state.ack_sends] scenarios: - name: evening_degraded @@ -1107,6 +1127,7 @@ capabilities: deployed: 'no' reachable: 'no' verified: 'no' + gap_class: 'capability exists but unreachable' components: [core.sink_ntfy, ext.ntfy] scenarios: - name: ntfy_403 @@ -1149,6 +1170,7 @@ capabilities: deployed: 'yes' reachable: 'yes' verified: 'partial' + gap_class: 'capability 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. @@ -1202,6 +1224,7 @@ capabilities: deployed: 'yes' reachable: 'yes' verified: 'partial' + gap_class: 'capability partial' components: [proc.mavweb, bnd.http_web] scenarios: scenario_note: >- @@ -1250,6 +1273,7 @@ capabilities: deployed: 'yes' reachable: 'yes' verified: 'no' + gap_class: 'capability partial' 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. @@ -1294,6 +1318,7 @@ capabilities: deployed: 'yes' reachable: 'yes' verified: 'no' + gap_class: 'capability exists but unverified' components: [core.stt_seam, proc.mavsttd, ext.whispercpp, ext.cw2_stt, bnd.worker] scenarios: scenario_note: >- @@ -1343,6 +1368,7 @@ capabilities: deployed: 'yes' reachable: 'yes' verified: 'no' + gap_class: 'capability exists but unverified' components: [core.tts_seam, proc.mavttsd, ext.piper, bnd.worker] scenarios: - name: tts_normalisation @@ -1386,6 +1412,7 @@ capabilities: deployed: 'yes' reachable: 'yes' verified: 'no' + gap_class: 'capability partial' components: [proc.mavwaked, cfg.systemd, ext.alsa] scenarios: - name: voice_push @@ -1428,6 +1455,7 @@ capabilities: deployed: 'no' reachable: 'no' verified: 'no' + gap_class: 'capability exists but unreachable' components: [core.capture, state.media_blobs] scenarios: - name: voice_push @@ -1470,6 +1498,7 @@ capabilities: deployed: 'no' reachable: 'no' verified: 'no' + gap_class: 'capability exists but unreachable' components: [core.speaker] deferred_note: >- **Deferred past v1**, owner's call 2026-08-15. See `docs/roadmap.md`. @@ -1505,6 +1534,7 @@ capabilities: deployed: 'yes' reachable: 'yes' verified: 'no' + gap_class: 'capability partial' components: [ext.nexus, core.ecosystem, bnd.http_ecosystem, state.ecosystem_traces] scenarios: - name: act_degraded @@ -1565,6 +1595,7 @@ capabilities: deployed: 'yes' reachable: 'yes' verified: 'no' + gap_class: 'capability partial' components: [ext.praxis, core.ecosystem, core.praxis_acts, core.q.attention, state.surfaced_items, state.ecosystem_traces] scenarios: - name: morning_missed @@ -1627,6 +1658,7 @@ capabilities: deployed: 'yes' reachable: 'yes' verified: 'no' + gap_class: 'capability exists but unverified' 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 @@ -1687,6 +1719,7 @@ capabilities: deployed: 'partial' reachable: 'partial' verified: 'no' + gap_class: 'capability exists but unreachable' components: [ext.homeassistant, core.home_worker, core.q.home] deferred_note: >- **Deferred past v1**, owner's call 2026-08-15. See `docs/roadmap.md`. @@ -1722,6 +1755,7 @@ capabilities: deployed: 'yes' reachable: 'yes' verified: 'partial' + gap_class: 'capability exists but unverified' components: [core.netscan, core.q.network] scenarios: - name: netscan_query @@ -1772,6 +1806,7 @@ capabilities: deployed: 'no' reachable: 'no' verified: 'no' + gap_class: 'capability missing' 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. @@ -1809,6 +1844,7 @@ capabilities: deployed: 'no' reachable: 'no' verified: 'no' + gap_class: 'capability exists but unreachable' 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. @@ -1861,6 +1897,7 @@ capabilities: deployed: 'yes' reachable: 'yes' verified: 'no' + gap_class: 'capability partial' components: [cfg.compose, cfg.mavend, proc.mavend, proc.mavweb, proc.mavsttd, proc.mavttsd, proc.mavpoll, proc.mavgpud, ext.netdata, ext.uptimekuma] scenarios: scenario_note: >- @@ -1910,6 +1947,7 @@ capabilities: deployed: 'yes' reachable: 'yes' verified: 'partial' + gap_class: 'capability partial' components: [state.db_file, state.db, state.db_tmpfs, proc.mavseal] scenarios: scenario_note: >- @@ -1961,6 +1999,7 @@ capabilities: deployed: 'yes' reachable: 'yes' verified: 'no' + gap_class: 'capability partial' components: [state.wrapped_key, state.passkey_file, core.daemon_lock, core.auth_gate, bnd.http_web] scenarios: - name: stepup_gate @@ -2011,6 +2050,7 @@ capabilities: deployed: 'partial' reachable: 'partial' verified: 'no' + gap_class: 'capability exists but unreachable' components: [core.modelswap, svc.llama_server] deferred_note: >- **Deferred past v1**, owner's call 2026-08-15. See `docs/roadmap.md`. @@ -2044,6 +2084,7 @@ capabilities: deployed: 'yes' reachable: 'yes' verified: 'no' + gap_class: 'capability exists but unverified' components: [proc.mavupdate] deferred_note: >- **Deferred past v1**, owner's call 2026-08-15. See `docs/roadmap.md`. @@ -2077,6 +2118,7 @@ capabilities: deployed: 'yes' reachable: 'yes' verified: 'partial' + gap_class: 'capability partial' components: [eval.gates, eval.router, eval.phrasing] scenarios: scenario_note: >- @@ -2127,6 +2169,7 @@ capabilities: deployed: 'no' reachable: 'no' verified: 'partial' + gap_class: 'capability exists but unreachable' 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. @@ -2190,6 +2233,7 @@ capabilities: deployed: 'partial' reachable: 'partial' verified: 'partial' + gap_class: 'capability exists but unreachable' components: [proc.mavcaldav, core.q.calendar] scenarios: - name: calendar_create @@ -2249,6 +2293,7 @@ capabilities: deployed: 'yes' reachable: 'yes' verified: 'no' + gap_class: 'capability partial' components: [core.crawl_worker, core.q.web] finding: >- politeness and robots are implemented. The scheduling policy is not written anywhere. @@ -2314,6 +2359,7 @@ capabilities: deployed: 'no' reachable: 'no' verified: 'no' + gap_class: 'capability missing' components: [] finding: >- summarisation exists inside the world chain and inside digestion, and nothing owns it as a capability he can ask for. @@ -2366,6 +2412,7 @@ capabilities: deployed: 'no' reachable: 'no' verified: 'no' + gap_class: 'capability missing' components: [] finding: >- there is no general webhook capability in either direction, and no doc says which direction is wanted. @@ -2418,6 +2465,7 @@ capabilities: deployed: 'yes' reachable: 'yes' verified: 'no' + gap_class: 'capability partial' 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. @@ -2486,6 +2534,7 @@ capabilities: deployed: 'no' reachable: 'no' verified: 'partial' + gap_class: 'capability missing' 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. @@ -2538,6 +2587,7 @@ capabilities: deployed: 'no' reachable: 'no' verified: 'no' + gap_class: 'capability missing' components: [] scenarios: - name: learn_from_dismissal @@ -2597,6 +2647,7 @@ capabilities: deployed: 'no' reachable: 'no' verified: 'no' + gap_class: 'capability missing' components: [] finding: >- nothing exists.