Two merge fixes on top of the branch:
- migrations: keep both new steps, snooze stays #8, the routine columns
become #9. Both agents had numbered theirs #8.
- accepting no longer takes a reminder id, on the web surface too. The
web accept path had the same one-shot-reminder bug the voice path did,
so both now just flip the status and let the tick loop schedule.
The test that asserted "accept creates a reminder and links it" asserted
the bug. It now asserts that accepting creates no reminder.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CGeSZxh1DCtRxmFVSYVGvJ
Accepting a routine gives the trigger loop a new standing reason to speak to
the human, so it is the same authority tier as enabling a tool and shares the
stepUpOK gate; dismiss only ever makes maven quieter, so it is ungated.
Look at handleRoutines and acceptRoutine in cmd/mavweb/main.go: accept creates
the recurring reminder, then links it via the new ipc AcceptProposedRoutine.
The page now says what maven noticed in her own words (pattern.PhraseRoutine).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CGeSZxh1DCtRxmFVSYVGvJ
Backlog item #3 (20-07-2026-BACKLOG.md). A morning routine is a checklist for
a daily window: several items, each evidenced by a fact key, completed in any
order, checked once near the end of the window. Modelling it as four
independent reminder timers would stack into exactly the kind of noise Maven is
supposed not to produce, so the engine nags at most once per day per routine
and only for what is actually still missing.
internal/morning follows the established pure-engine pattern (loop, routine,
pattern): no store, no clock of its own. Evaluate answers "what's still
missing" at any point; Due decides whether to nag. The impurity — reading
facts under the store lock, holding the last-nudge map across ticks — stays in
the tick driver, which calls Due each tick exactly as it does for loop.Rule
and routine.Routine.
Completion evidence is a fact key's latest non-voided value timestamped inside
today's window, so manual ("выпил воды", voice-tapped) and inferred (another
daemon writing the same key) are indistinguishable and both count. Weekdays
scopes which days a routine applies to, so weekday/weekend variants are two
routine rows rather than a special case in the engine.
Exposed read-only: a MorningStatus RPC over ipc, and a /morning page in mavweb
built on the same server-rendered shape as /trace — no live-update loop, since
checklist state moves on the scale of minutes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X5JApcrCRVGmqrxnhynSik
15 root markdown files, ~4,900 lines against ~33,000 lines of Go, with at least
three pairs contradicting each other. When five documents describe the
architecture, the code becomes the only trustworthy one — which defeats the
point of having them. That drift is why the resident-model question had four
incompatible answers.
SPEC.md, maven.md and ROADMAP.md are deduped into DESIGN.md rather than
concatenated, with a "Superseded" section carrying eight retired decisions and
what replaced each: classifier-owns-the-route (the cascade is still the live
path, but as a stopgap, not a design to extend), faster-whisper/vosk/silero,
the small-model phrasing claim, sqlcipher, the Kotlin/Spring sketches,
obsidian->chroma, script deployment, and FloorEnrollment. Superseded material
is kept and marked rather than deleted, so it cannot read as current.
SESSION-05/06-07-2026.md and PLANS.md are removed outright — git history holds
them, and both were verified tracked before deletion.
Go doc comments citing the deleted files are repointed to the equivalent
DESIGN.md sections. Several asserted designs that were already retired, so the
claims are corrected and not just relinked: stt.go named faster-whisper as
production (it is whisper.cpp), tts.go named silero (it is piper), intent.go
still described the classifier as owning the route, and stale vosk/chroma
vocabulary is replaced. ECOSYSTEM-SPEC.md references are deliberately
untouched — that is a different document, and a naive grep for SPEC.md matches
it.
Root markdown drops from 4,880 to ~3,700 lines. The review's ~1,500 target is
not reachable while keeping the files it also said to keep — those alone are
2,553 lines — so trimming further needs a separate decision on
MAVEN_ECOSYSTEM_ARCHITECTURE.md and PROGRESS.md.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X5JApcrCRVGmqrxnhynSik
Add a read-only /ecosystem page that consumes the sibling services'
JSON APIs (Nexus entities, Praxis attention, Hexis capabilities),
fetched concurrently with honest per-panel error states. Siblings stay
headless — mavweb is their human surface (arch §16). Wired via mavweb
-nexus/-praxis/-hexis flags; mavweb joins the ecosystem compose network.
Fix mobile horizontal overflow across all pages: .content is a flex
child with default min-width:auto, so it refused to shrink below the
tables' intrinsic width. min-width:0 lets wide tables pan inside .scroll
instead of dragging the page sideways. Verified via CDP geometry check
(scrollWidth === clientWidth at 430px).
Also includes in-progress Ethos UI redesign, ecosystem deploy compose,
and planning docs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Add CalendarEvents method to recordingAPI in auth_test.go
- Add CalendarEvents method to fakeCore in handlers_test.go
Co-Authored-By: opencode <opencode@anthropic.com>
- digest queue no longer dropped on failed dispatch (retry next tick)
- collapsed reminders marked fired/rescheduled only after digest delivers
- /tools step-up gate skipped when WebAuthn is not configured (was 403 forever)
- passkey credential store rolls back memory on persist failure
- auth_test fake updated for TickTrace (branch build break)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ASstMtsZWLSRcD1Tq8T68Q
Add cron expression support for recurring reminders using robfig/cron/v3.
Changes:
- Migration #2: ALTER TABLE reminders ADD COLUMN cron TEXT + next_fire_ts INTEGER
- Reminder struct: add Cron and NextFireTs fields
- scanReminder helper extracts full row including nullable cron
- CreateReminder: accept optional cron param, store next_fire_ts = fire_ts
- DueReminders: query on next_fire_ts instead of fire_ts
- RescheduleReminder: new method — parse cron, compute next fire, update
next_fire_ts or mark fired if no more valid times
- Dispatcher: call RescheduleReminder for cron reminders, MarkReminder for
one-shots (preserving existing behavior for ID=0 digest skip)
- ReminderCompleter interface: add RescheduleReminder method
- storeAPI adapter: forward RescheduleReminder
- All callers updated: CreateReminder signature includes cron param
- Tests: TestRecurringReminder (store), TestDispatchRecurringReminderReschedules
- Existing tests updated for new signature
Add a 'scope' TEXT column (default 'homelab') to the tools table so tools
can be namespaced by scope (e.g. "homelab:restart", "datacenter:reboot").
Backward-compat: bare name defaults to "homelab" scope.
Changes:
- Migration #1: ALTER TABLE tools ADD COLUMN scope
- store.Tool: add Scope field, update all SQL and scanTool()
- ipc.Tool DTO and request types: add Scope field
- CoreAPI interface: pass scope in ProposeTool/EnableTool
- storeAPI adapters: forward scope
- cmd/mavend/voice: pass scope (empty → homelab)
- cmd/mavweb/tools: show scope column in UI tables, hidden fields
- All tests updated for scope field
- Migration test made dynamic (startVer = len(migrations))
recordingAPI did not implement the new CoreAPI.RevertFact method
added in a02e10f, causing a build failure in auth tests.
Also adds coverage.out to .gitignore.
- handlers_test.go: first tests for cmd/mavweb (feature-ranking #2). Covers
the /tools enable/disable surface (arg parsing, error mapping, html escaping)
and the webauthn handler contracts (method guards, malformed input). 14 cases.
Verified the enable path is genuinely gated: an un-asserted call fails at the
mavend IPC boundary (Requirement(EnableTool)=AuthStepUp), so mavweb stays a
trust-nothing pass-through and core mediates.
- policy.go: DisableTool now also requires AuthStepUp. It mutates the same tool
allowlist as EnableTool and is a lever to silence a security-relevant tool;
gating allowlist mutation uniformly beats a split rule. ProposeTool stays
maven-callable (no passkey). Corrects the stale api.go comment that claimed
all three gated.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>