items 5-7: passkey step-up, tools enable/disable, note RAG — end to end

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>
This commit is contained in:
kami
2026-07-03 18:41:13 +04:00
parent 36233058dd
commit 6239eca243
21 changed files with 1492 additions and 50 deletions
+8 -2
View File
@@ -176,6 +176,10 @@ type enableToolReq struct {
Destructive bool `json:"destructive"`
Ts time.Time `json:"ts"`
}
type disableToolReq struct {
Name string `json:"name"`
}
type lookupToolReq struct {
Name string `json:"name"`
}
@@ -215,10 +219,12 @@ type CoreAPI interface {
// ProposeTool drafts an inert 'proposed' tool scaffold (maven-callable);
// returns whether a new proposal was written. EnableTool fills cmd +
// destructive and flips to 'enabled' — the human-only "enable" act, gated
// at AuthStepUp (see auth/policy.go). LookupTool/ListTools read them.
// destructive and flips status to 'enabled'. DisableTool reverts an
// enabled tool back to proposed (it stays in the store, won't run).
// All three gate at AuthStepUp. LookupTool/ListTools read them.
ProposeTool(ctx context.Context, name, utterance string, ts time.Time) (bool, error)
EnableTool(ctx context.Context, name string, cmd []string, destructive bool, ts time.Time) error
DisableTool(ctx context.Context, name string) error
LookupTool(ctx context.Context, name string) (Tool, error)
ListTools(ctx context.Context, status string) ([]Tool, error)
}
+8
View File
@@ -242,6 +242,14 @@ func (c *Client) EnableTool(ctx context.Context, name string, cmd []string, dest
return c.call(ctx, MethodEnableTool, enableToolReq{Name: name, Cmd: cmd, Destructive: destructive, Ts: ts}, nil)
}
func (c *Client) DisableTool(ctx context.Context, name string) error {
return c.call(ctx, MethodDisableTool, disableToolReq{Name: name}, nil)
}
func (c *Client) AssertStepUp(ctx context.Context) error {
return c.call(ctx, MethodAssertStepUp, nil, nil)
}
func (c *Client) LookupTool(ctx context.Context, name string) (Tool, error) {
var t Tool
if err := c.call(ctx, MethodLookupTool, lookupToolReq{Name: name}, &t); err != nil {
+28
View File
@@ -153,6 +153,10 @@ func (a *storeAPI) EnableTool(ctx context.Context, name string, cmd []string, de
return mapErr(a.s.EnableTool(ctx, name, cmd, destructive, ts))
}
func (a *storeAPI) DisableTool(ctx context.Context, name string) error {
return mapErr(a.s.DisableTool(ctx, name))
}
func (a *storeAPI) LookupTool(ctx context.Context, name string) (Tool, error) {
t, err := a.s.LookupTool(ctx, name)
if err != nil {
@@ -267,6 +271,12 @@ type Server struct {
// change to gain or lose the seam.
Check CheckFunc
// StepUp — optional handler for MethodAssertStepUp. When a real Session
// (PasskeySession) is wired, the daemon sets this to session.Assert so a
// module (mavweb) can assert a user-verification gesture over IPC. Nil ⇒
// MethodAssertStepUp returns ErrUnknownMethod (same as pre-stepup floor).
StepUp StepUpFunc
// now is injected so tests can drive time; the loop already works in
// absolute ts supplied by callers, so this isn't load-bearing for live ops.
}
@@ -279,6 +289,11 @@ type Server struct {
// auth doesn't need to leak implementation into ipc.
type CheckFunc func(ctx context.Context, m Method, params json.RawMessage) error
// StepUpFunc — records a user-verification gesture. Set by the daemon when
// a real Session is wired (PasskeySession); nil means not available.
// MethodAssertStepUp dispatch calls this instead of going through CoreAPI.
type StepUpFunc func(ctx context.Context) error
// Listen creates a Server bound to path. path's parent dir must exist and be
// 0700 (we chmod it if we own it); the socket file itself is created 0600 so
// only the same unix user can connect — the current "auth floor", same radius
@@ -560,6 +575,13 @@ func (s *Server) dispatch(ctx context.Context, req Request) (json.RawMessage, er
}
return marshalResult(nil), s.api.EnableTool(ctx, p.Name, p.Cmd, p.Destructive, p.Ts)
case MethodDisableTool:
var p disableToolReq
if err := unmarshalParams(req.Params, &p); err != nil {
return nil, err
}
return marshalResult(nil), s.api.DisableTool(ctx, p.Name)
case MethodLookupTool:
var p lookupToolReq
if err := unmarshalParams(req.Params, &p); err != nil {
@@ -585,6 +607,12 @@ func (s *Server) dispatch(ctx context.Context, req Request) (json.RawMessage, er
}
return marshalResult(listToolsResp{Tools: out}), nil
case MethodAssertStepUp:
if s.StepUp != nil {
return marshalResult(nil), s.StepUp(ctx)
}
return nil, fmt.Errorf("%w: %s", ErrUnknownMethod, req.Method)
default:
return nil, fmt.Errorf("%w: %s", ErrUnknownMethod, req.Method)
}
+2
View File
@@ -30,6 +30,8 @@ const (
MethodRecentNotes Method = "recent_notes"
MethodProposeTool Method = "propose_tool"
MethodEnableTool Method = "enable_tool"
MethodDisableTool Method = "disable_tool"
MethodAssertStepUp Method = "assert_stepup"
MethodLookupTool Method = "lookup_tool"
MethodListTools Method = "list_tools"
)