Completes the three in-flight open items and fixes the away-fallthrough bug.
Item 7 — passkey step-up (WebAuthn):
- internal/webauthn: ES256/P-256 register + assert with real ecdsa signature
verification, minimal CBOR/COSE decode, PasskeySession (L2→L3 on assert,
decays after TTL). Drop the RS256 offer we can't verify (register-ok/
assert-fail trap). Verify rpIdHash + UP/UV flags in FinishAssertion — UV is
the step-up gesture. Round-trip test with negative cases (tampered sig,
missing UV, wrong origin).
- cmd/mavweb: /auth/passkey enroll+assert page (the only surface that can do
a WebAuthn gesture) + the four begin/finish endpoints. Without this the
daemon's PasskeySession swap leaves /tools enable permanently blocked.
- daemon wires PasskeySession as the auth Session + srv.StepUp; policy gates
MethodAssertStepUp at AuthRead.
Item 5 — tools page: DisableTool through store/ipc/client/wire; /tools grows a
disable action and a link to the passkey page. Lifecycle test.
Item 6 — note RAG: PhraseQuery on the phraser (LLM-composed answer over top-k
notes, raw-notes fallback); IntentQuery routes through it. Stub returns a
deterministic summary.
Item 2 — away-fallthrough: on ErrVoiceNoSession the dispatcher now reroutes
through the AWAY table (sev3→ntfy, sev4→telegram-repeat-til-ack, sev≤2→drop)
instead of silently dropping / mis-routing to the present-list remainder.
Covers DispatchNudge + DispatchReminder. 4 tests.
Also: re-add ProposeTool to CoreAPI (dropped in a comment rewrite), fix
missing imports + a duplicate block left mid-edit, drop dead AssertStepUpFunc,
gitignore /mavcaldav.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds QuietHours config section (start/end as HH:MM local time). The loop's
gatherer checks the window each tick: if now falls within [start, end), the
State.QuietHours flag is set to true regardless of the config fact (which
the voice toggle writes independently). Both sources activate quiet —
schedule AND toggle.
Handles midnight-crossing windows (23:00-08:00). The gate already reads
State.QuietHours for care nudge suppression — no gate change needed.
Polls Radicale for today's events, writes calendar_busy and calendar_event
facts through CoreAPI. Only writes on value change (same append-only
discipline as mavpoll).
Usage: mavcaldav -socket <core> -url <radicale> -user <u> -pass <p>
Flags: -interval (default 5m), -timeout (default 10s).
Fires immediately on start, then on interval.
iCal parser supports UTC and local DTSTART/DTEND, skips all-day events.
PROTOCOL.md: generated from internal/voice/wire.go — documents the
voice wire format (TCP, length-prefixed JSON, methods, pushes, errors)
for multi-client implementors. Must stay in sync with wire.go.
dispatcher: when voice sink returns ErrVoiceNoSession, skip voice and
continue to remaining channels instead of aborting. sev4-present
already has ntfy in the routing table (continues naturally). sev1-3
present have only voice — the loop ends with no dispatches, which
matches the spec (care/ops-soft drop on no-voice).
voicesink: maps voice.ErrNoSession to delivery.ErrVoiceNoSession so
the dispatcher can detect it without importing the voice package.
The dispatcher already routes sev4-away → telegram. The gap is purely
the runtime ErrNoSession fallthrough in voicesink.go. Points agent at
the existing TODO, tells it to converge not duplicate.
Addresses Claude's review:
- Acceptance criteria (done-when) for all 8 open items
- PROTOCOL.md must be generated from wire.go, not freehand
- Resolved CalDAV: Radicale, new cmd/mavcaldav module
- Reordered to core→capabilities→harden priority
- Hard guard: multi-user schema is do-not-touch this phase
- Dropped Nextcloud mention
piper_handler: close stdin/stdout pipes on Start() failure and on
WriteString error instead of leaking fds. Propagate WriteString error.
worker/client: log SetDeadline errors instead of discarding them.
voice/session: pushAudio marshals params inline and returns the marshal
error instead of swallowing it via mustParams (removed).
tool/matcher: log ListTools errors instead of silently returning an
empty allowlist that refuses every act.
config: applyDefaults now sets RouterThreshold and ToolTimeout defaults
so consumers self-contained defaults are belt-and-suspenders.
worker/server.go: dispatch now receives a per-connection context instead of
context.Background(), so handler cancellation propagates on conn close.
voice/server.go: same — per-conn context fed through safeDispatch into
HandlePushToTalk instead of context.Background().
store/reminders.go: propagate LastInsertId error.
store/nudges.go: propagate LastInsertId and RowsAffected errors.
store/tools.go: propagate RowsAffected error.
config/config.go: applyDefaults now respects StateDir when set, using it as
the base for empty DBPath/SocketPath instead of silently ignoring it.
phraser/llmphraser.go: close stderr pipe fd when cmd.Start() fails.
- resolveQuietToggle runs in HandlePushToTalk before the router so
'тихий режим' works regardless of classifier confidence.
- whisper_full() runs in a goroutine with ctx.Done() select so the
handler returns promptly on timeout/shutdown.
- StubReplier.IntentQuery no longer claims query is unimplemented.