Session 2 step 2. docs/spec.md states 51 capabilities one at a time. Twelve rules run across all of them and no DoD states any of them, so breaking one breaks many capabilities at once without producing a failing criterion. Each is marked explicit, implied or unresolved, with evidence. Nothing wanted is invented where the sources are silent. Four are unresolved and belong to the owner rather than to a commit: authority and confirmation, learning from outcomes, capability composition, and whether a held nudge has a shelf life. Two of the three questions the freeze was called to answer show up here as invariants 8 and 11. Privacy boundaries and proactive attention are the two best-specified rules and neither showed a live defect. Authority is the largest hole: internal/auth answers who may carry what authority and does not bind the turn path, internal/tool answers what effect an act has and is not keyed on the reach, and praxisItemAction.handle has no gate at all. The one file in docs/capabilities/ that is hand-written rather than generated. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
12 KiB
The cross-cutting rules the 51 capabilities imply
Hand-written. The one file in this directory that is not generated.
docs/spec.md states 51 capabilities one at a time. Twelve rules run across all
of them, and no capability's definition of done states any of these. A rule
broken here breaks many capabilities at once, which is why it does not show up
as one failing criterion.
Each rule carries a mark. A rule split between what is written down and what is not carries both, and says which half is which.
| mark | meaning |
|---|---|
explicit |
a source states the rule and names its enforcement point |
implied |
capabilities depend on it, no source states it, and the code decides it case by case |
unresolved |
the sources do not answer it. A product question, not a defect |
Nothing wanted is invented where the sources are silent. An unresolved rule
needs the owner, not a commit.
Evidence is docs/architecture/findings.md for the code reading,
docs/evals/2026-08-26-capability-baseline.md for what ran, and the file itself
where the rule is written down.
1. Continuity across turns and across reaches
Implied. Continuity within one reach is built. Continuity across reaches is not, and nothing states whether it should be.
dialogue.NewPersistentSessionStore carries follow-up slots across turns and
across a restart. The clarify store is a per-reach stack and is deliberately not
persisted (findings.md 7.5, Vikunja #385).
Across reaches there is no shared thread. mavweb hardcodes one conversation id
for the whole web reach, which is not continuity but the absence of separation:
a clarify parked by one probe was still parked for the next, and the first field
run had to be discarded for it (docs/capabilities/README.md, "Two things the
harness learned the hard way").
What breaks: a question asked by voice and answered on the web has no thread to attach to. No capability's DoD asks for one, so nothing scores this.
The product question: is a conversation per reach, or one conversation the reaches are windows onto?
2. Memory and correction semantics
Explicit for the row, implied for the namespace.
Supersede is written down and enforced: a correction points voids_id at the row
it replaces, and valid-time is the ts column (internal/store/schema.sql).
CLAUDE.md states the embedder contract, EmbedQuery and EmbedPassage, and
calling plain Embed on a note is named as a bug.
Who may write a key is not written anywhere. facts has nine writers and no
owner, and two of them store things that are not observations: crawl:hash:* is
a fetch watermark and cooldown:<rule> is a tuning parameter (findings.md
1.1). The source column keeps them apart by convention, and the CHECK
constraint covers only kind. notes has six writers, one of them a LAN scan
whose records then compete by cosine similarity with things he said
(findings.md 1.2).
What breaks: recall answers a question about him with a fetch watermark.
queryFactByKey reads the same table back as an answer.
3. Current context and presence
Explicit and partly false at runtime.
Presence is one hysteresis bucket rewritten each tick (state.presence_state),
and the dispatcher's routing table is a pure function of severity and presence.
One input is permanently wrong. loop.State.CalendarBusy reads
facts(kind=env, source=caldav:*) and mavcaldav is commented out of
docker-compose.yml, so the "do not nag mid-meeting" suppressor is always false
(findings.md 8.2). The compose file says so, which makes it a known gap.
What breaks: every interruption decision that should have deferred to a meeting. It fails open, toward interrupting.
4. Proactive attention
Explicit, and the one prohibition is stated.
CLAUDE.md: no automatic attention-to-action path. Digestion may summarise
Praxis and may not call Hexis. At most one nudge candidate per tick, and the
restraint gate is a pure function over the rule set (core.rules).
Restraint is decided twice on purpose (findings.md 2.3), and blocked candidates
are held durably in digest_entries rather than dropped.
What breaks: nothing observed. This is the best-specified rule in the list.
5. Interruption policy
Explicit for the choice, implied for the outcome.
docs/handler-wiring.md owns the dispatch decision, and the table over
(severity, presence) is pure. Delivery intent is recorded in
delivery_attempts before the external send, so a crash leaves a pending row
rather than a lost one.
What is not stated is what a held nudge owes the user later. digest_entries
holds blocked candidates and two separate mechanisms carry the word digest
(findings.md 2.4). Nothing says when a held item expires instead of
resurfacing.
The product question: does a held nudge have a shelf life?
6. Clarification and follow-up ownership
Implied. Who owns an open question, and for how long, is decided by three components and stated by none.
runTurn step 1 fires an expired-clarify notice, the clarify store is a
per-reach stack, and the pre-route ladder may claim the turn before routing
(findings.md 2.2). A restart drops a parked request silently, because the
notice path reads the store that is gone (findings.md 7.5).
Measured: one park survived five consecutive turns, turns 9 through 13, and was
released by a path other than отмена
(docs/evals/2026-08-26-capability-baseline.md).
What breaks: a question she asked stays open across unrelated turns, and neither the ladder nor the store says whose job it is to close it.
7. Degradation and honesty
Explicit as a rule, and the rule contradicts itself in practice.
CLAUDE.md states both halves. Fall back silently when the fallback would only
do the job better. Name the gap when the resident model cannot do the job at
all. docs/spec.md writes every v1 DoD at "voice-reachable and honest", where
honest means naming the gap and never filling it with a guess.
Four silent degradations stack on one turn: workstation model to resident model,
CW2 to mavsttd, routing heads to LLM router to classifier, and search to Kiwix to
a named page to the model's own weights (findings.md 8.1). Each is argued
individually. Together a reply can be the resident model routing a worse
transcript with the classifier as a floor, answering from its weights, and
nothing in the reply distinguishes that from the best case.
What breaks: the boundary between "only better" and "cannot do the job" is not drawn anywhere, so the stack decides it by accident.
The product question: at what depth of fallback does silence stop being honest?
8. Authority and confirmation
Unresolved, and this is the largest hole in the list.
Two systems each answer half and never meet. internal/auth answers who may
carry what authority and does not bind the reactive turn path at all
(findings.md 6.3). internal/tool answers what effect a capability has and
what proof it demands, runs on every act, and is not keyed on the reach
(findings.md 6.3b). Neither has the other's reach.
Two representations of reach exist and both are ignored.
internal/voice/server.go:198 defaults an empty p.Surface and a
client-asserted one survives to a handler that never reads it. :148 hardcodes
SurfacePCClient for every connection. req.Surface is request payload on a
plaintext wire with no auth, so any client can claim pc_client. It must not
become an authorization input as it stands.
One path has no gate at all. praxisItemAction.handle
(cmd/mavend/ecosystem_acts.go:158) reads dec.Slots.Value and calls straight
through. Acknowledge, resolve, ignore and pin are remote mutations that run on
first hearing, with no tier and no confirm turn.
CLAUDE.md states the rule the code does not implement: LLM output is not
authorization, and a confirmation binds capability id, target entity, arguments,
requester and expiry.
What breaks: no one can currently state the authority property of a Maven
turn. origin × effect × evidence → permit is the target shape and nothing
computes it.
9. Privacy boundaries
Explicit, and it is the best-enforced rule here.
CLAUDE.md: the owner's data first, then the world. His notes and facts are
never search input, only the utterance leaves the box. The personal boundary is a
query source with boundary: true, and queryWalk reads
Decision.SourceAnchored for that source and no other.
The exception is deliberate and recorded. The boundary guesses, so naming
SourceWorld drops it, and only a stage 0 grammar may do that (owner's call,
V-666). No component reads another component's database, and Praxis attention
comes over HTTP rather than from its SQLite file.
What breaks: nothing observed. The one caveat is that queryWalk takes
sources out and moves none, which is the safety argument, and it holds only as
long as the table's order stays load-bearing.
10. Learning from outcomes
Unresolved. One loop exists, two are specified with no package, and nothing says whether learning is a product goal.
Built: state.nudges is the restraint memory and the only input to the tick
loop's autotune, which writes cooldown:<rule> back into facts.
state.routing_labels holds owner corrections of a route.
Not built: learning-the-style and learning-from-mistakes have no package and
no component. docs/spec.md gives each a DoD written at what done would look
like. Both score code_present: no.
One criterion passes by absence. "No model weights change and no training set is built" is a negative, and nothing being built satisfies it. The generator reports this as an anomaly rather than counting it as progress.
The product question: is behavioural learning wanted, or is the negative criterion the whole of the intent?
11. Capability composition
Implied and absent. Every capability is specified alone and the turn is single-claim by construction.
core.action_table dispatches one intent to one handler, and a handler returning
the empty string hands the turn on. queryWalk stops at the first source that
claims. Two independent arbitrations already decide one turn, with a third
running before both (findings.md 2.1, 2.2).
command-chaining fails all three of its criteria with reason wiring missing.
The chain in internal/router is the world chain and the source chain, not
command chaining.
internal/claim is the beginning of a vocabulary for this and is called by
nothing (findings.md 6.1). It carries a live defect: Claim.Coverage returns
1.0 for a claim that extracted nothing, because claimSpans includes
Slots.Text unconditionally and fillSlots backfills the raw utterance into
Text (findings.md 6.3d).
What breaks: "напомни мне и запиши это" performs one of the two and says nothing about the other.
The product question: what is the single unit that competes for a turn. This is one of the three the freeze was called to answer.
12. Persistence across restart
Implied. Four stores made four different choices and no source states the rule.
| state | survives a restart | evidence |
|---|---|---|
| dialogue sessions | yes | dialogue.NewPersistentSessionStore |
| clarify store | no, deliberately | findings.md 7.5, Vikunja #385 |
| decision ring | no, in-memory bounded at 25 | internal/decision/ring.go:11 |
| routing traces | yes, retained 14 days | CLAUDE.md |
| tick memo | no, in-process and argued for one field | findings.md 7.4 |
| surfaced items | no, and no TTL | findings.md 7.3 |
Two of these are principled. The decision ring holds his words and is bounded on purpose. The clarify store's reasoning is filed. The other four are not decided anywhere.
What breaks: less than it looks. surfacedItems has no TTL, and the source
comment argues that a stale ordinal resolves to an item Praxis reports as already
acknowledged, which is harmless because Praxis is the arbiter (findings.md
7.3). The cost is that the same absence of a written rule produced one argued
choice and three unargued ones.
What this file is for
Session 2 step 2 of docs/plans/26-capability-ledger-and-baseline.md. It feeds
docs/capabilities/gaps.md, where every architecture concern must name the
capability or invariant it affects.
Four rules are unresolved and they are the owner's, not a commit's: authority
and confirmation, learning from outcomes, capability composition, and the shelf
life of a held nudge. Two of the three questions the freeze was called to answer
appear here as invariant 8 and invariant 11.