From d52f60c54e9173e5aa505fa07882ce956066ff26 Mon Sep 17 00:00:00 2001 From: kami Date: Mon, 6 Jul 2026 04:20:16 +0400 Subject: [PATCH] maven: fix test mocks for CalendarEvents interface (verification) - Add CalendarEvents method to recordingAPI in auth_test.go - Add CalendarEvents method to fakeCore in handlers_test.go Co-Authored-By: opencode --- cmd/mavenclient/main.go | 6 +-- cmd/mavend/main.go | 2 +- cmd/mavend/tick.go | 6 +-- cmd/mavend/tick_test.go | 2 +- cmd/mavend/voice.go | 30 +++++------ cmd/mavpoll/main_test.go | 2 +- cmd/mavsttd/silence_test.go | 6 +-- cmd/mavttsd/main.go | 2 +- cmd/mavttsd/piper_handler.go | 16 +++--- cmd/mavweb/handlers_test.go | 3 ++ internal/audio/audio.go | 2 +- internal/audio/audio_test.go | 2 +- internal/audio/pcmwav.go | 4 +- internal/auth/auth_test.go | 3 ++ internal/config/config.go | 20 +++---- internal/config/config_test.go | 2 +- internal/config/helpers_test.go | 2 +- internal/delivery/channel.go | 10 ++-- internal/delivery/dispatcher_test.go | 2 +- .../delivery/telegramsink/telegramsink.go | 2 +- .../telegramsink/telegramsink_test.go | 2 +- internal/delivery/voicesink/voicesink.go | 2 +- internal/dialogue/session.go | 2 - internal/ipc/api.go | 40 +++++++------- internal/ipc/client.go | 2 +- internal/ipc/frame.go | 2 +- internal/ipc/ipc_test.go | 2 +- internal/ipc/server.go | 16 +++--- internal/ipc/wire.go | 54 +++++++++---------- internal/loop/explain.go | 12 ++--- internal/loop/feedback.go | 2 +- internal/loop/feedback_test.go | 8 +-- internal/loop/gather.go | 2 +- internal/loop/loop.go | 2 +- internal/loop/loop_test.go | 14 ++--- internal/loop/rules.go | 42 +++++++-------- internal/loop/state.go | 2 +- internal/phraser/phraser.go | 2 +- internal/phraser/phraser_test.go | 10 ++-- internal/router/intent.go | 22 ++++---- internal/router/onnxembedder.go | 8 +-- internal/store/crypt.go | 6 +-- internal/store/facts.go | 2 +- internal/store/nudges.go | 2 +- internal/store/presence.go | 8 +-- internal/store/presence_state.go | 2 +- internal/store/presence_test.go | 2 +- internal/store/reminders.go | 16 +++--- internal/store/store.go | 2 +- internal/store/store_test.go | 2 +- internal/stt/stt.go | 12 ++--- internal/stt/stt_test.go | 2 +- internal/tts/tts.go | 6 +-- internal/tts/tts_test.go | 2 +- internal/voice/client.go | 8 +-- internal/voice/errors.go | 10 ++-- internal/voice/frame.go | 2 +- internal/voice/replier.go | 2 +- internal/voice/server.go | 2 +- internal/voice/session.go | 21 ++++---- internal/voice/voice_test.go | 2 +- internal/voice/wire.go | 38 ++++++------- internal/webauthn/cbor.go | 4 +- internal/webauthn/webauthn.go | 16 +++--- internal/worker/client.go | 6 +-- internal/worker/frame.go | 2 +- internal/worker/handler.go | 2 +- internal/worker/jobs.go | 8 +-- internal/worker/server.go | 2 +- internal/worker/wire.go | 4 +- internal/worker/worker_test.go | 2 +- 71 files changed, 287 insertions(+), 280 deletions(-) diff --git a/cmd/mavenclient/main.go b/cmd/mavenclient/main.go index fa6ef8a..d64c756 100644 --- a/cmd/mavenclient/main.go +++ b/cmd/mavenclient/main.go @@ -21,8 +21,8 @@ // delivery end-to-end. The reference for "the most-recently-active client // plays it": run one, fire a tick, see the file appear. // -// mavenclient -listen -out-prefix /tmp/maven-nudge- -// # then trigger a tick; /tmp/maven-nudge-1.wav, -2.wav ... appear +// mavenclient -listen -out-prefix /tmp/maven-nudge- +// # then trigger a tick; /tmp/maven-nudge-1.wav, -2.wav ... appear package main import ( @@ -160,4 +160,4 @@ func jsonUnmarshal(b []byte, v any) error { return json.Unmarshal(b, v) } var _ = strconv.Atoi var _ io.Reader = (io.Reader)(nil) var _ = net.IPv4 -var _ = time.Second \ No newline at end of file +var _ = time.Second diff --git a/cmd/mavend/main.go b/cmd/mavend/main.go index 26af1a7..50649ef 100644 --- a/cmd/mavend/main.go +++ b/cmd/mavend/main.go @@ -230,4 +230,4 @@ func run(args []string) error { wg.Wait() log.Printf("mavend: bye") return nil -} \ No newline at end of file +} diff --git a/cmd/mavend/tick.go b/cmd/mavend/tick.go index dc57977..e7c0aa0 100644 --- a/cmd/mavend/tick.go +++ b/cmd/mavend/tick.go @@ -45,8 +45,8 @@ type tickLoop struct { phraser phraser.Phraser rules []loop.Rule - tickInterval time.Duration - repeatInterval time.Duration + tickInterval time.Duration + repeatInterval time.Duration autotuneInterval time.Duration // 0 ⇒ autotune disabled (gatherer falls back to static Base) // digestCfg — the digest/batching config. nil ⇒ every nudge is sent @@ -429,4 +429,4 @@ func toIPCGateDetail(d loop.GateDetail) ipc.GateDetail { Presence: d.Presence, InertKeysMissing: d.InertKeysMissing, } -} \ No newline at end of file +} diff --git a/cmd/mavend/tick_test.go b/cmd/mavend/tick_test.go index cd3a10a..795b09e 100644 --- a/cmd/mavend/tick_test.go +++ b/cmd/mavend/tick_test.go @@ -134,7 +134,7 @@ func TestTickCooldownSuppressesSecondSend(t *testing.T) { sink := &fakeSink{} tl := newTestTickLoop(t, st, sink, nil) - tl.tick(ctx, now) // fires + tl.tick(ctx, now) // fires tl.tick(ctx, now.Add(time.Minute)) // still within 30m cooldown ⇒ suppressed if len(sink.sends) != 1 { diff --git a/cmd/mavend/voice.go b/cmd/mavend/voice.go index c00fc7a..abb77ae 100644 --- a/cmd/mavend/voice.go +++ b/cmd/mavend/voice.go @@ -50,17 +50,17 @@ import ( "log" "os" "path/filepath" - "strings" "strconv" + "strings" "sync" "time" "github.com/kami/maven/internal/audio" - "github.com/kami/maven/internal/memory" "github.com/kami/maven/internal/config" "github.com/kami/maven/internal/delivery" "github.com/kami/maven/internal/delivery/voicesink" "github.com/kami/maven/internal/ipc" + "github.com/kami/maven/internal/memory" "github.com/kami/maven/internal/phraser" "github.com/kami/maven/internal/router" "github.com/kami/maven/internal/stt" @@ -207,18 +207,18 @@ func wireVoice(cfg *config.Config, coreAPI ipc.CoreAPI, phr phraser.Phraser) (*v // ----- the handler (the reactive path; closes over stt / tts / router / coreAPI / memory) ----- h := &reactiveHandler{ - stt: transcriber, - tts: synthesizer, - router: rtr, - embedder: emb, - api: coreAPI, - tools: exec, - phraser: phr, - replier: voice.NewStubReplier(), - now: time.Now, - weatherProvider: weatherProvider, - weatherLocation: weatherLocation, - memStore: memStore, + stt: transcriber, + tts: synthesizer, + router: rtr, + embedder: emb, + api: coreAPI, + tools: exec, + phraser: phr, + replier: voice.NewStubReplier(), + now: time.Now, + weatherProvider: weatherProvider, + weatherLocation: weatherLocation, + memStore: memStore, } // ----- the server (TCP listener) ----- @@ -968,4 +968,4 @@ func jsonStringImpl(s string) string { } b = append(b, '"') return string(b) -} \ No newline at end of file +} diff --git a/cmd/mavpoll/main_test.go b/cmd/mavpoll/main_test.go index 09bf3cd..2b79156 100644 --- a/cmd/mavpoll/main_test.go +++ b/cmd/mavpoll/main_test.go @@ -53,7 +53,7 @@ func TestParseMaxHandshake(t *testing.T) { {"", 0}, {"pubkeyAAA\t0\n", 0}, // never handshaked {"pubkeyAAA\t1700000000\n", 1700000000}, - {"pubkeyAAA\t1700000000\npubkeyBBB\t1700000500\n", 1700000500}, // max wins + {"pubkeyAAA\t1700000000\npubkeyBBB\t1700000500\n", 1700000500}, // max wins {"wg0\tpubkeyAAA\t1700000000\nwg0\tpubkeyBBB\t0\n", 1700000000}, // 'all' 3-field form } for _, c := range cases { diff --git a/cmd/mavsttd/silence_test.go b/cmd/mavsttd/silence_test.go index 6f20b08..71bffbf 100644 --- a/cmd/mavsttd/silence_test.go +++ b/cmd/mavsttd/silence_test.go @@ -27,10 +27,10 @@ func TestGateReason(t *testing.T) { gated bool // true ⇒ expect a non-empty reason (dropped) }{ {"empty", nil, true}, - {"too short", sine(100, 0.5), true}, // 100ms < 300ms + {"too short", sine(100, 0.5), true}, // 100ms < 300ms {"long but silent", make([]float32, whisperSampleRate), true}, // 1s of zeros - {"long but near-silent", sine(500, 0.005), true}, // rms ~0.0035 < floor - {"real speech-ish", sine(500, 0.3), false}, // loud enough, long enough + {"long but near-silent", sine(500, 0.005), true}, // rms ~0.0035 < floor + {"real speech-ish", sine(500, 0.3), false}, // loud enough, long enough } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { diff --git a/cmd/mavttsd/main.go b/cmd/mavttsd/main.go index 92df454..88e3898 100644 --- a/cmd/mavttsd/main.go +++ b/cmd/mavttsd/main.go @@ -126,4 +126,4 @@ func defaultSocket(name string) string { return name } return home + "/.local/share/maven/" + name -} \ No newline at end of file +} diff --git a/cmd/mavttsd/piper_handler.go b/cmd/mavttsd/piper_handler.go index 2853683..6a39b0e 100644 --- a/cmd/mavttsd/piper_handler.go +++ b/cmd/mavttsd/piper_handler.go @@ -13,19 +13,19 @@ import ( ) type piperHandler struct { - piperPath string - modelPath string - configPath string - espeakData string + piperPath string + modelPath string + configPath string + espeakData string tashkeelModel string } func newPiperHandler(piperPath, modelPath, espeakData, tashkeelModel string) *piperHandler { return &piperHandler{ - piperPath: piperPath, - modelPath: modelPath, - configPath: modelPath + ".json", - espeakData: espeakData, + piperPath: piperPath, + modelPath: modelPath, + configPath: modelPath + ".json", + espeakData: espeakData, tashkeelModel: tashkeelModel, } } diff --git a/cmd/mavweb/handlers_test.go b/cmd/mavweb/handlers_test.go index 0a4d7d3..0ed376d 100644 --- a/cmd/mavweb/handlers_test.go +++ b/cmd/mavweb/handlers_test.go @@ -116,6 +116,9 @@ func (f *fakeCore) RecentFacts(_ context.Context, _ int) ([]ipc.Fact, error) { } return f.facts, nil } +func (f *fakeCore) CalendarEvents(_ context.Context, _, _ time.Time) ([]ipc.Fact, error) { + return nil, nil +} func (f *fakeCore) RecentNudges(_ context.Context, _ int) ([]ipc.Nudge, error) { if f.nudgesErr != nil { diff --git a/internal/audio/audio.go b/internal/audio/audio.go index c39f59f..7bb0a0d 100644 --- a/internal/audio/audio.go +++ b/internal/audio/audio.go @@ -83,4 +83,4 @@ func (f Format) IsValid() bool { f.Channels == PCM16kMono.Channels && f.SampleBits == PCM16kMono.SampleBits && f.Encoding == PCM16kMono.Encoding -} \ No newline at end of file +} diff --git a/internal/audio/audio_test.go b/internal/audio/audio_test.go index 710739b..fea8f2e 100644 --- a/internal/audio/audio_test.go +++ b/internal/audio/audio_test.go @@ -101,4 +101,4 @@ func TestPCMFromWAVRejectsNonCanonical(t *testing.T) { if _, _, err := PCMFromWAV(wav); err == nil { t.Fatalf("non-PCM format should error") } -} \ No newline at end of file +} diff --git a/internal/audio/pcmwav.go b/internal/audio/pcmwav.go index 210918a..4889fa3 100644 --- a/internal/audio/pcmwav.go +++ b/internal/audio/pcmwav.go @@ -106,7 +106,7 @@ func WAVFromPCM(format Format, pcm []byte) ([]byte, error) { // fmt chunk copy(out[12:16], []byte("fmt ")) binary.LittleEndian.PutUint32(out[16:20], 16) // fmt chunk size - binary.LittleEndian.PutUint16(out[20:22], 1) // PCM + binary.LittleEndian.PutUint16(out[20:22], 1) // PCM binary.LittleEndian.PutUint16(out[22:24], uint16(format.Channels)) binary.LittleEndian.PutUint32(out[24:28], uint32(format.SampleRate)) byteRate := uint32(format.SampleRate) * uint32(format.Channels) * uint32(format.SampleBits) / 8 @@ -118,4 +118,4 @@ func WAVFromPCM(format Format, pcm []byte) ([]byte, error) { copy(out[36:40], []byte("data")) binary.LittleEndian.PutUint32(out[40:44], uint32(len(pcm))) return out, nil -} \ No newline at end of file +} diff --git a/internal/auth/auth_test.go b/internal/auth/auth_test.go index 3370a66..287d9e0 100644 --- a/internal/auth/auth_test.go +++ b/internal/auth/auth_test.go @@ -354,6 +354,9 @@ func (r *recordingAPI) RecentOutcomes(_ context.Context, _ string, _ int) ([]str func (r *recordingAPI) RecentFacts(_ context.Context, _ int) ([]ipc.Fact, error) { return nil, nil } +func (r *recordingAPI) CalendarEvents(_ context.Context, _, _ time.Time) ([]ipc.Fact, error) { + return nil, nil +} func (r *recordingAPI) RecentNudges(_ context.Context, _ int) ([]ipc.Nudge, error) { return nil, nil } diff --git a/internal/config/config.go b/internal/config/config.go index c2cca42..a6f4941 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -186,8 +186,8 @@ type VoiceConfig struct { // WeatherConfig configures the weather provider for voice queries. type WeatherConfig struct { - Provider string `json:"provider,omitempty"` // "open-meteo" or "" → stub - DefaultLocation string `json:"default_location,omitempty"` // e.g. "Moscow" + Provider string `json:"provider,omitempty"` // "open-meteo" or "" → stub + DefaultLocation string `json:"default_location,omitempty"` // e.g. "Moscow" } // ToolConfig — one enabled tool. Name is the spoken verb ("restart"); Cmd is @@ -223,11 +223,11 @@ type DigestConfig struct { // - NCtx defaults to 2048. // - Timeout defaults to 30s per request. type PhraserConfig struct { - ModelPath string `json:"model_path"` - BinPath string `json:"bin_path,omitempty"` - Listen string `json:"listen,omitempty"` - NGpuLayers int `json:"n_gpu_layers,omitempty"` - NCtx int `json:"n_ctx,omitempty"` + ModelPath string `json:"model_path"` + BinPath string `json:"bin_path,omitempty"` + Listen string `json:"listen,omitempty"` + NGpuLayers int `json:"n_gpu_layers,omitempty"` + NCtx int `json:"n_ctx,omitempty"` Timeout Duration `json:"timeout,omitempty"` } @@ -237,9 +237,9 @@ type PhraserConfig struct { // floor HashEmbedder stub. Model_path is the ONNX model file, tokenizer_path // is tokenizer.json (Unigram), lib_path is the ONNX Runtime shared library. type EmbedderConfig struct { - ModelPath string `json:"model_path,omitempty"` + ModelPath string `json:"model_path,omitempty"` TokenizerPath string `json:"tokenizer_path,omitempty"` - LibPath string `json:"lib_path,omitempty"` + LibPath string `json:"lib_path,omitempty"` } // WorkerConfig — a unix-socket worker module connection. Used by Stt and @@ -428,4 +428,4 @@ func defaultRuntimeDir() string { // /run/user/$UID is the typical answer; without XDG_RUNTIME_DIR, fall back // to the data dir (still works; just not tmpfs-clearance-on-reboot clean). return filepath.Join(defaultDataDir()) -} \ No newline at end of file +} diff --git a/internal/config/config_test.go b/internal/config/config_test.go index c86f8f4..be31962 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -187,4 +187,4 @@ func TestDurationRoundTrip(t *testing.T) { if d2 != d { t.Errorf("round-trip = %v, want %v", d2, d) } -} \ No newline at end of file +} diff --git a/internal/config/helpers_test.go b/internal/config/helpers_test.go index 30a70c1..bed64a7 100644 --- a/internal/config/helpers_test.go +++ b/internal/config/helpers_test.go @@ -17,4 +17,4 @@ func writeFile(t *testing.T, path, body string) error { } } return os.WriteFile(path, []byte(body), 0o600) -} \ No newline at end of file +} diff --git a/internal/delivery/channel.go b/internal/delivery/channel.go index 3b66d14..eeee0a2 100644 --- a/internal/delivery/channel.go +++ b/internal/delivery/channel.go @@ -5,17 +5,19 @@ // - routing = f(severity, presence). presence decides REACHABILITY; severity // decides INSISTENCE. need both. // -// | | present | away | -// | sev1–2 (care) | voice | drop | -// | sev3 (ops soft) | voice | ntfy, once | -// | sev4 (ops hard) | voice + ntfy | telegram, repeat til ack | +// | | present | away | +// | sev1–2 (care) | voice | drop | +// | sev3 (ops soft) | voice | ntfy, once | +// | sev4 (ops hard) | voice + ntfy | telegram, repeat til ack | // // - sev ≤ 2 drops on away, sev ≥ 3 holds. "a missed water nudge is noise; // a missed backup-failure isn't." +// // - away channels (ntfy/telegram) leave the box — the one path that crosses // "never phones home," through your relay. MINIMAL BODY — "disk low on // homesrv," not detail. don't make notifications a shoulder-surf exfil // surface. +// // - reminders are a SEPARATE class — two delivery paths. reminders bypass // the restraint gate ("wake me 7" fires in quiet hours; that's the point). // snooze still applies. voice when present, ntfy when away. fire once. diff --git a/internal/delivery/dispatcher_test.go b/internal/delivery/dispatcher_test.go index 1fdc9eb..04a2f81 100644 --- a/internal/delivery/dispatcher_test.go +++ b/internal/delivery/dispatcher_test.go @@ -84,7 +84,7 @@ func (f *fakeReminderCompleter) RescheduleReminder(_ context.Context, id int64, } type fakeAck struct { - acked map[string]bool + acked map[string]bool lastSent map[string]time.Time } diff --git a/internal/delivery/telegramsink/telegramsink.go b/internal/delivery/telegramsink/telegramsink.go index 23449bb..66b0849 100644 --- a/internal/delivery/telegramsink/telegramsink.go +++ b/internal/delivery/telegramsink/telegramsink.go @@ -196,4 +196,4 @@ func (s *Sink) Send(ctx context.Context, d delivery.Sendable) error { // token never leaves the sink, no logging. func (s *Sink) sendMessageURL() string { return s.base + "/bot" + s.cfg.BotToken + "/sendMessage" -} \ No newline at end of file +} diff --git a/internal/delivery/telegramsink/telegramsink_test.go b/internal/delivery/telegramsink/telegramsink_test.go index 25ae761..5c378f4 100644 --- a/internal/delivery/telegramsink/telegramsink_test.go +++ b/internal/delivery/telegramsink/telegramsink_test.go @@ -399,4 +399,4 @@ func TestReminderSendUsesSamePath(t *testing.T) { if req.Text != "wake up" { t.Fatalf("reminder text: want 'wake up', got %q", req.Text) } -} \ No newline at end of file +} diff --git a/internal/delivery/voicesink/voicesink.go b/internal/delivery/voicesink/voicesink.go index bd3c39f..748197a 100644 --- a/internal/delivery/voicesink/voicesink.go +++ b/internal/delivery/voicesink/voicesink.go @@ -106,4 +106,4 @@ func (s *Sink) Send(ctx context.Context, send delivery.Sendable) error { // Format.IsValid which is a method on the imported audio.Format). The alias // below keeps the import alive even if a future refactor moves the only // reference. Today, the synthesizer's audio.Audio directly flows through. -var _ = audio.PCM16kMono \ No newline at end of file +var _ = audio.PCM16kMono diff --git a/internal/dialogue/session.go b/internal/dialogue/session.go index 4161bd2..b2495f9 100644 --- a/internal/dialogue/session.go +++ b/internal/dialogue/session.go @@ -105,5 +105,3 @@ func InheritSlots(prev, cur Slots) Slots { } return out } - - diff --git a/internal/ipc/api.go b/internal/ipc/api.go index 4de18c1..4fac4f9 100644 --- a/internal/ipc/api.go +++ b/internal/ipc/api.go @@ -12,14 +12,14 @@ import ( // Fact — one observation. Ts is valid-time (true-as-of), as in store. type Fact struct { - ID int64 `json:"id"` - Ts time.Time `json:"ts"` - Kind string `json:"kind"` // "self" | "env" | "config" - Key string `json:"key"` - Value string `json:"value"` // raw json if structured - Source string `json:"source"` // tap:*|infer:*|poll:*|ambient|promote|feedback - Confidence float64 `json:"confidence"` - VoidsID *int64 `json:"voids_id,omitempty"` + ID int64 `json:"id"` + Ts time.Time `json:"ts"` + Kind string `json:"kind"` // "self" | "env" | "config" + Key string `json:"key"` + Value string `json:"value"` // raw json if structured + Source string `json:"source"` // tap:*|infer:*|poll:*|ambient|promote|feedback + Confidence float64 `json:"confidence"` + VoidsID *int64 `json:"voids_id,omitempty"` } // Bucket — presence hysteresis state: "present" | "away". @@ -66,9 +66,9 @@ type Reminder struct { // routing and tone. presence = reachability, NOT wakefulness (spec). Loop // reads probes + computes score itself; modules get the resolved snapshot. type Presence struct { - Bucket Bucket `json:"bucket"` - Score float64 `json:"score"` - Updated time.Time `json:"updated"` + Bucket Bucket `json:"bucket"` + Score float64 `json:"score"` + Updated time.Time `json:"updated"` } // WriteFactReq — the only state mutation a capture/tool module performs. @@ -315,19 +315,19 @@ func CallerFrom(ctx context.Context) (Caller, bool) { // Sentinel errors. Mirror store's 1:1 so module code reads the same whether // in-process or over the wire. The store adapter translates store.* → these. var ( - ErrNoFact = errors.New("ipc: no fact for key") - ErrConfidence = errors.New("ipc: confidence must be in (0.0, 1.0]") - ErrVoidsMissing = errors.New("ipc: voids_id does not reference an existing fact") - ErrNudgeNotFound = errors.New("ipc: nudge not found") - ErrNudgeOutcome = errors.New("ipc: nudge already resolved") + ErrNoFact = errors.New("ipc: no fact for key") + ErrConfidence = errors.New("ipc: confidence must be in (0.0, 1.0]") + ErrVoidsMissing = errors.New("ipc: voids_id does not reference an existing fact") + ErrNudgeNotFound = errors.New("ipc: nudge not found") + ErrNudgeOutcome = errors.New("ipc: nudge already resolved") ErrReminderNotFound = errors.New("ipc: reminder not found") - ErrReminderState = errors.New("ipc: reminder not in a mutable state") - ErrUnknownMethod = errors.New("ipc: unknown method") - ErrBadParams = errors.New("ipc: bad params") + ErrReminderState = errors.New("ipc: reminder not in a mutable state") + ErrUnknownMethod = errors.New("ipc: unknown method") + ErrBadParams = errors.New("ipc: bad params") // ErrForbidden — the caller's authority doesn't cover this call. The // auth layer's only wire-exported verdict: surface caps the layer, or a // write was out-of-scope, or step-up was required but not asserted. The // text ErrForbidden carries is derived during dispatch (from auth.ErrForbidden // via fmt.Errorf %w wrapping); the wire carries codeForbidden. ErrForbidden = errors.New("ipc: forbidden") -) \ No newline at end of file +) diff --git a/internal/ipc/client.go b/internal/ipc/client.go index 26b5c73..5344cdf 100644 --- a/internal/ipc/client.go +++ b/internal/ipc/client.go @@ -372,4 +372,4 @@ func (c *Client) RevertFact(ctx context.Context, key string) (int64, error) { } // Compile-time check: *Client satisfies CoreAPI. -var _ CoreAPI = (*Client)(nil) \ No newline at end of file +var _ CoreAPI = (*Client)(nil) diff --git a/internal/ipc/frame.go b/internal/ipc/frame.go index c24ef6b..311e7b4 100644 --- a/internal/ipc/frame.go +++ b/internal/ipc/frame.go @@ -83,4 +83,4 @@ func readFrame(r io.Reader, v any) error { return fmt.Errorf("ipc: unmarshal frame: %w", err) } return nil -} \ No newline at end of file +} diff --git a/internal/ipc/ipc_test.go b/internal/ipc/ipc_test.go index 9883ef6..db79005 100644 --- a/internal/ipc/ipc_test.go +++ b/internal/ipc/ipc_test.go @@ -423,4 +423,4 @@ func mustJSON(v any) []byte { panic(err) } return b -} \ No newline at end of file +} diff --git a/internal/ipc/server.go b/internal/ipc/server.go index 353372f..1091be9 100644 --- a/internal/ipc/server.go +++ b/internal/ipc/server.go @@ -280,9 +280,9 @@ type Server struct { api CoreAPI path string - ln net.Listener - wg sync.WaitGroup - done chan struct{} + ln net.Listener + wg sync.WaitGroup + done chan struct{} // Check — optional authorization hook. dispatch runs it BEFORE method // dispatch, with the raw params, so the auth layer can make verdicts @@ -343,10 +343,10 @@ func Listen(path string, api CoreAPI) (*Server, error) { return nil, fmt.Errorf("ipc: chmod socket: %w", err) } return &Server{ - api: api, - path: path, - ln: ln, - done: make(chan struct{}), + api: api, + path: path, + ln: ln, + done: make(chan struct{}), }, nil } @@ -754,4 +754,4 @@ func peerCaller(c net.Conn) (Caller, bool) { return Caller{}, false } return Caller{Uid: int32(cred.Uid), Pid: int32(cred.Pid)}, true -} \ No newline at end of file +} diff --git a/internal/ipc/wire.go b/internal/ipc/wire.go index 62f87bd..28cd482 100644 --- a/internal/ipc/wire.go +++ b/internal/ipc/wire.go @@ -13,38 +13,38 @@ import ( type Method string const ( - MethodWriteFact Method = "write_fact" - MethodLatestFact Method = "latest_fact" + MethodWriteFact Method = "write_fact" + MethodLatestFact Method = "latest_fact" MethodLatestFactBySource Method = "latest_fact_by_source" - MethodSince Method = "since" - MethodPresence Method = "presence" - MethodCreateReminder Method = "create_reminder" - MethodMarkReminder Method = "mark_reminder" - MethodRecordNudge Method = "record_nudge" - MethodResolveNudge Method = "resolve_nudge" - MethodRecentOutcomes Method = "recent_outcomes" - MethodRecentFacts Method = "recent_facts" - MethodCalendarEvents Method = "calendar_events" - MethodRecentNudges Method = "recent_nudges" - MethodWriteNote Method = "write_note" - MethodQueryNotes Method = "query_notes" - 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" - MethodRevertFact Method = "revert_fact" - MethodTickTrace Method = "tick_trace" + MethodSince Method = "since" + MethodPresence Method = "presence" + MethodCreateReminder Method = "create_reminder" + MethodMarkReminder Method = "mark_reminder" + MethodRecordNudge Method = "record_nudge" + MethodResolveNudge Method = "resolve_nudge" + MethodRecentOutcomes Method = "recent_outcomes" + MethodRecentFacts Method = "recent_facts" + MethodCalendarEvents Method = "calendar_events" + MethodRecentNudges Method = "recent_nudges" + MethodWriteNote Method = "write_note" + MethodQueryNotes Method = "query_notes" + 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" + MethodRevertFact Method = "revert_fact" + MethodTickTrace Method = "tick_trace" ) // Request — one frame from module to core. Params is the JSON-encoded argument // struct for Method (see api.go for the per-method shapes). The server // unmarshals Params based on Method; an unknown Method ⇒ ErrUnknownMethod. type Request struct { - Method Method `json:"m"` - Params json.RawMessage `json:"p,omitempty"` + Method Method `json:"m"` + Params json.RawMessage `json:"p,omitempty"` } // Response — one frame from core back to module. Exactly one of Result/Error @@ -52,7 +52,7 @@ type Request struct { // scalar, a struct, or null for void methods). type Response struct { Result json.RawMessage `json:"r,omitempty"` - Error *RpcError `json:"e,omitempty"` + Error *RpcError `json:"e,omitempty"` } // RpcError — a typed wire error. Code is one of the sentinel codes below; @@ -132,4 +132,4 @@ func rpcErr(err error) *RpcError { return &RpcError{Code: c, Message: err.Error()} } return &RpcError{Code: c} -} \ No newline at end of file +} diff --git a/internal/loop/explain.go b/internal/loop/explain.go index fd6ac3d..0b87f82 100644 --- a/internal/loop/explain.go +++ b/internal/loop/explain.go @@ -27,12 +27,12 @@ type RuleTrace struct { // GateDetail — snapshot of the values the gate checked. type GateDetail struct { - SnoozeUntil *time.Time `json:"snooze_until,omitempty"` - CooldownUntil *time.Time `json:"cooldown_until,omitempty"` - QuietHours bool `json:"quiet_hours"` - CalendarBusy bool `json:"calendar_busy"` - Presence string `json:"presence"` // "present"|"away" - InertKeysMissing []string `json:"inert_keys_missing,omitempty"` + SnoozeUntil *time.Time `json:"snooze_until,omitempty"` + CooldownUntil *time.Time `json:"cooldown_until,omitempty"` + QuietHours bool `json:"quiet_hours"` + CalendarBusy bool `json:"calendar_busy"` + Presence string `json:"presence"` // "present"|"away" + InertKeysMissing []string `json:"inert_keys_missing,omitempty"` } // ExplainGate runs the same checks as Gate() but records the first blocker. diff --git a/internal/loop/feedback.go b/internal/loop/feedback.go index 91842ac..a48af45 100644 --- a/internal/loop/feedback.go +++ b/internal/loop/feedback.go @@ -119,4 +119,4 @@ func ParseCooldownFact(f store.Fact) (time.Duration, bool) { func MarshalCooldown(d time.Duration) string { b, _ := json.Marshal(int64(d)) // int64 marshal never errors return string(b) -} \ No newline at end of file +} diff --git a/internal/loop/feedback_test.go b/internal/loop/feedback_test.go index 07dd238..effe7ae 100644 --- a/internal/loop/feedback_test.go +++ b/internal/loop/feedback_test.go @@ -97,10 +97,10 @@ func TestFeedbackKeyMatchesRuleName(t *testing.T) { func TestParseCooldownFactRoundTrip(t *testing.T) { d := 45 * time.Minute f := store.Fact{ - Key: "cooldown:water", + Key: "cooldown:water", Source: FeedbackSource, - Value: MarshalCooldown(d), - Ts: refTime(), + Value: MarshalCooldown(d), + Ts: refTime(), } got, ok := ParseCooldownFact(f) if !ok { @@ -149,4 +149,4 @@ func repeat(v string, n int) []string { out[i] = v } return out -} \ No newline at end of file +} diff --git a/internal/loop/gather.go b/internal/loop/gather.go index 0caa6a1..405aa0f 100644 --- a/internal/loop/gather.go +++ b/internal/loop/gather.go @@ -242,4 +242,4 @@ func collapseReminders(due []store.Reminder) []store.Reminder { Status: "pending", Collapsed: due, }} -} \ No newline at end of file +} diff --git a/internal/loop/loop.go b/internal/loop/loop.go index f2baafb..9b8dbee 100644 --- a/internal/loop/loop.go +++ b/internal/loop/loop.go @@ -134,4 +134,4 @@ func CooldownFor(base time.Duration, lastSend time.Time) time.Time { return time.Time{} // never sent → no cooldown active } return lastSend.Add(base) -} \ No newline at end of file +} diff --git a/internal/loop/loop_test.go b/internal/loop/loop_test.go index 12abc93..d6837c8 100644 --- a/internal/loop/loop_test.go +++ b/internal/loop/loop_test.go @@ -129,9 +129,9 @@ func TestTickCooldownSuppresses(t *testing.T) { now := refTime() // 4h since water (would fire) — but cooldown until now+10min. Suppressed. s := State{ - Now: now, - Presence: store.Present, - Facts: map[string]store.Fact{ + Now: now, + Presence: store.Present, + Facts: map[string]store.Fact{ "water": factAt("water", "tap:water", `"250ml"`, now.Add(-4*time.Hour)), }, CooldownUntil: map[string]time.Time{ @@ -197,9 +197,9 @@ func TestGateNoDataInertShutsUp(t *testing.T) { // is missing in the snapshot — gate must still return false. s := State{Now: refTime(), Presence: store.Present} iwantfire := Rule{ - Name: "x", - Severity: Sev1, - Predicate: func(State) bool { return true }, + Name: "x", + Severity: Sev1, + Predicate: func(State) bool { return true }, InertWhenNoData: []string{"missing_key"}, } if Gate(s, iwantfire) { @@ -379,4 +379,4 @@ func TestGathererUsesFeedbackTunedCooldown(t *testing.T) { if d, ok := ParseCooldownFact(fb); !ok || d != tuned { t.Fatalf("ParseCooldownFact round-trip: want %v ok, got %v ok=%v", tuned, d, ok) } -} \ No newline at end of file +} diff --git a/internal/loop/rules.go b/internal/loop/rules.go index 0521da2..f7a6344 100644 --- a/internal/loop/rules.go +++ b/internal/loop/rules.go @@ -15,10 +15,10 @@ import "time" // auto-tuner scales it over time (mostly ignored → lengthen; acted → leave). // Bounds belong at the daemon-config level; here we just carry the base. type Rule struct { - Name string - Severity Severity - Cooldown // base cooldown + bounded-duration envelope for the auto-tuner - Predicate func(State) bool + Name string + Severity Severity + Cooldown // base cooldown + bounded-duration envelope for the auto-tuner + Predicate func(State) bool // InertWhenNoData — most rules should be silent when their substrate key is // missing (since(key)==null → don't fire). If the predicate already encodes @@ -43,9 +43,9 @@ type Cooldown struct { // Inert when no water fact exists at all (shuts up when uncertain). func WaterRule() Rule { return Rule{ - Name: "water", - Severity: Sev1, - Cooldown: Cooldown{Base: 30 * time.Minute, Min: 15 * time.Minute, Max: 6 * time.Hour}, + Name: "water", + Severity: Sev1, + Cooldown: Cooldown{Base: 30 * time.Minute, Min: 15 * time.Minute, Max: 6 * time.Hour}, InertWhenNoData: []string{"water"}, Predicate: func(s State) bool { d, ok := s.Since("water") @@ -60,16 +60,16 @@ func WaterRule() Rule { // MealRule — sev1 care: if ≥6h since an `meal` fact, fire. Inert without data. func MealRule() Rule { return Rule{ - Name: "meal", - Severity: Sev1, - Cooldown: Cooldown{Base: 60 * time.Minute, Min: 30 * time.Minute, Max: 8 * time.Hour}, + Name: "meal", + Severity: Sev1, + Cooldown: Cooldown{Base: 60 * time.Minute, Min: 30 * time.Minute, Max: 8 * time.Hour}, InertWhenNoData: []string{"meal"}, Predicate: func(s State) bool { d, ok := s.Since("meal") if !ok { return false } - return d >= 6 * time.Hour + return d >= 6*time.Hour }, } } @@ -79,9 +79,9 @@ func MealRule() Rule { // Inert unless both exist — can't claim continuous activity without both anchors. func BreakRule() Rule { return Rule{ - Name: "break", - Severity: Sev2, - Cooldown: Cooldown{Base: 45 * time.Minute, Min: 20 * time.Minute, Max: 4 * time.Hour}, + Name: "break", + Severity: Sev2, + Cooldown: Cooldown{Base: 45 * time.Minute, Min: 20 * time.Minute, Max: 4 * time.Hour}, InertWhenNoData: []string{"desk_active", "break"}, Predicate: func(s State) bool { dDesk, ok1 := s.Since("desk_active") @@ -101,9 +101,9 @@ func BreakRule() Rule { // a compromised poller writing under a different source can't forge the trigger. func ServiceDownRule() Rule { return Rule{ - Name: "service_down", - Severity: Sev4, - Cooldown: Cooldown{Base: 15 * time.Minute, Min: 5 * time.Minute, Max: 1 * time.Hour}, + Name: "service_down", + Severity: Sev4, + Cooldown: Cooldown{Base: 15 * time.Minute, Min: 5 * time.Minute, Max: 1 * time.Hour}, InertWhenNoData: []string{"service_down"}, Predicate: func(s State) bool { f, ok := s.Fact("service_down") @@ -123,9 +123,9 @@ func ServiceDownRule() Rule { // the hard page. Provenance-scoped to poll:netdata. func NetdataCriticalRule() Rule { return Rule{ - Name: "netdata_critical", - Severity: Sev3, - Cooldown: Cooldown{Base: 20 * time.Minute, Min: 10 * time.Minute, Max: 2 * time.Hour}, + Name: "netdata_critical", + Severity: Sev3, + Cooldown: Cooldown{Base: 20 * time.Minute, Min: 10 * time.Minute, Max: 2 * time.Hour}, InertWhenNoData: []string{"netdata_alarm"}, Predicate: func(s State) bool { f, ok := s.Fact("netdata_alarm") @@ -148,4 +148,4 @@ func DefaultRules() []Rule { ServiceDownRule(), NetdataCriticalRule(), } -} \ No newline at end of file +} diff --git a/internal/loop/state.go b/internal/loop/state.go index db57723..f17b074 100644 --- a/internal/loop/state.go +++ b/internal/loop/state.go @@ -106,4 +106,4 @@ func (s State) Since(key string) (time.Duration, bool) { return 0, true } return s.Now.Sub(f.Ts), true -} \ No newline at end of file +} diff --git a/internal/phraser/phraser.go b/internal/phraser/phraser.go index fa97c27..d3f1520 100644 --- a/internal/phraser/phraser.go +++ b/internal/phraser/phraser.go @@ -202,4 +202,4 @@ func sevLabel(s loop.Severity) string { default: return "alarm" } -} \ No newline at end of file +} diff --git a/internal/phraser/phraser_test.go b/internal/phraser/phraser_test.go index 9b54b79..5097328 100644 --- a/internal/phraser/phraser_test.go +++ b/internal/phraser/phraser_test.go @@ -17,8 +17,8 @@ func TestPhraseNudgeWaterMentionsDuration(t *testing.T) { now := time.Now().UTC() earlier := now.Add(-4 * time.Hour) st := loop.State{ - Now: now, - Facts: map[string]store.Fact{"water": {Key: "water", Ts: earlier, Source: "tap:water", Value: `"250ml"`}}, + Now: now, + Facts: map[string]store.Fact{"water": {Key: "water", Ts: earlier, Source: "tap:water", Value: `"250ml"`}}, } c := loop.Candidate{Rule: loop.WaterRule(), Severity: loop.Sev1, State: st} pn, err := NewStub().PhraseNudge(context.Background(), c) @@ -60,10 +60,10 @@ func TestPhraseNudgeMealNoDataStillPhrases(t *testing.T) { func TestPhraseNudgeBreakDeskDuration(t *testing.T) { now := time.Now().UTC() st := loop.State{ - Now: now, + Now: now, Facts: map[string]store.Fact{ "desk_active": {Key: "desk_active", Ts: now.Add(-30 * time.Second)}, - "break": {Key: "break", Ts: now.Add(-95 * time.Minute)}, + "break": {Key: "break", Ts: now.Add(-95 * time.Minute)}, }, } c := loop.Candidate{Rule: loop.BreakRule(), Severity: loop.Sev2, State: st} @@ -220,4 +220,4 @@ func TestStubProducesDeliveryTypes(t *testing.T) { // ----------------------------- helpers -------------------------------------- // (the per-rule templates change output shape; assert via strings.Contains // against salient fragments, not exact strings — keeps the tests robust to -// tone tweaks in the Stub.) \ No newline at end of file +// tone tweaks in the Stub.) diff --git a/internal/router/intent.go b/internal/router/intent.go index 134ccfc..9bea415 100644 --- a/internal/router/intent.go +++ b/internal/router/intent.go @@ -7,16 +7,16 @@ // a classifier owns the route; the SLM stays in its phrasing lane. same // boundary as "rules decide, llm phrases," extended to the reactive path. // - a CASCADE, not classifier-vs-deterministic — layers: -// stage 0 — exact match (regex/grammar). wake-word + known command -// grammar. "maven, restart nginx" hits the allowlist directly, -// skips the classifier. lowest latency — the vosk command path. -// stage 1 — intent classifier. embed utterance, nearest-centroid over -// labeled intents. one forward pass, ~30ms cpu, similarity score. -// stage 2 — slot extraction, per intent. classification gives *what kind*, -// not *the args*. reminders need a datetime, acts need fn+params. -// stage 3 — confidence gate. below threshold → clarify, don't guess. -// same pattern as since(key)==null → don't fire: a misrouted -// fact is a confident wrong write — worse than a gap. +// stage 0 — exact match (regex/grammar). wake-word + known command +// grammar. "maven, restart nginx" hits the allowlist directly, +// skips the classifier. lowest latency — the vosk command path. +// stage 1 — intent classifier. embed utterance, nearest-centroid over +// labeled intents. one forward pass, ~30ms cpu, similarity score. +// stage 2 — slot extraction, per intent. classification gives *what kind*, +// not *the args*. reminders need a datetime, acts need fn+params. +// stage 3 — confidence gate. below threshold → clarify, don't guess. +// same pattern as since(key)==null → don't fire: a misrouted +// fact is a confident wrong write — worse than a gap. // - save-where is the routing axis: act | reminder | fact | note | query. // - misroute correction = new centroid example — append-only, grows the // classifier as used. same shape as nudges.outcome tuning cooldowns. @@ -88,7 +88,7 @@ type Slots struct { // same shape as since(key)==null → don't fire for the loop. type Decision struct { Utterance string - Stage int // 0 exact-match, 1 classified, 2 slots-extracted, 3 clarify-gated + Stage int // 0 exact-match, 1 classified, 2 slots-extracted, 3 clarify-gated Intent Intent Confidence float64 // 1.0 for stage-0; classifier cosine similarity for 1+ Slots Slots diff --git a/internal/router/onnxembedder.go b/internal/router/onnxembedder.go index fc0b8a8..a7356ee 100644 --- a/internal/router/onnxembedder.go +++ b/internal/router/onnxembedder.go @@ -228,10 +228,10 @@ func (t *unigramTokenizer) tokenize(text string) []int64 { } type cand struct { - start int - end int - id int64 - score float64 + start int + end int + id int64 + score float64 } func (t *unigramTokenizer) encodeWord(word string) []int64 { diff --git a/internal/store/crypt.go b/internal/store/crypt.go index fc93757..9cb96f0 100644 --- a/internal/store/crypt.go +++ b/internal/store/crypt.go @@ -44,10 +44,10 @@ import ( // (new dep) — do NOT bolt on sha256(passphrase). const ( - cryptMagic = "MVNC1\x00" // 6-byte file magic; version in the trailing byte + cryptMagic = "MVNC1\x00" // 6-byte file magic; version in the trailing byte cryptMagicLen = len(cryptMagic) - nonceLen = 12 // AES-GCM standard nonce - keyLen = 32 // AES-256 + nonceLen = 12 // AES-GCM standard nonce + keyLen = 32 // AES-256 ) // ErrKeyLen — the supplied encryption key was not exactly 32 bytes. diff --git a/internal/store/facts.go b/internal/store/facts.go index bf09432..5388eb2 100644 --- a/internal/store/facts.go +++ b/internal/store/facts.go @@ -253,4 +253,4 @@ func scanFact(r rowScanner) (Fact, error) { f.Kind = FactKind(kind) f.VoidsID = voids return f, nil -} \ No newline at end of file +} diff --git a/internal/store/nudges.go b/internal/store/nudges.go index 2d4d65c..2a0db37 100644 --- a/internal/store/nudges.go +++ b/internal/store/nudges.go @@ -181,4 +181,4 @@ func (s *Store) LastNudge(ctx context.Context, rule string) (Nudge, error) { n.OutcomeTs = outcomeTs } return n, nil -} \ No newline at end of file +} diff --git a/internal/store/presence.go b/internal/store/presence.go index 547dca1..4f8345c 100644 --- a/internal/store/presence.go +++ b/internal/store/presence.go @@ -35,9 +35,9 @@ import ( // Signal — one presence signal with hand-tuned fresh-weight and decay τ. type Signal struct { - Key string - Weight float64 - TauMin float64 + Key string + Weight float64 + TauMin float64 } // PresenceSignals — the three signals. Iterate in stable order. @@ -110,4 +110,4 @@ func Resolve(score float64, last Bucket) Bucket { // ResolveCold — convenience for the very first tick after daemon cold-start. // presence_state has no row; we begin as Away, the fail-closed outcome. -func ResolveCold(score float64) Bucket { return Resolve(score, Away) } \ No newline at end of file +func ResolveCold(score float64) Bucket { return Resolve(score, Away) } diff --git a/internal/store/presence_state.go b/internal/store/presence_state.go index 85fac06..e7be488 100644 --- a/internal/store/presence_state.go +++ b/internal/store/presence_state.go @@ -58,4 +58,4 @@ func (s *Store) PresenceProbes(ctx context.Context) ([]SignalProbe, error) { probes = append(probes, SignalProbe{Key: sig.Key, LastTs: &t}) } return probes, nil -} \ No newline at end of file +} diff --git a/internal/store/presence_test.go b/internal/store/presence_test.go index f15a203..01d3835 100644 --- a/internal/store/presence_test.go +++ b/internal/store/presence_test.go @@ -142,4 +142,4 @@ func TestNegativeClockSkewClampsToFresh(t *testing.T) { if got > 0.90+1e-9 { t.Fatalf("clock skew clamps: want <= weight(0.90), got %f", got) } -} \ No newline at end of file +} diff --git a/internal/store/reminders.go b/internal/store/reminders.go index f968036..3c031e7 100644 --- a/internal/store/reminders.go +++ b/internal/store/reminders.go @@ -12,13 +12,13 @@ import ( // Reminder — user-stated future intent. fires once or recurring (if cron set). type Reminder struct { - ID int64 - CreatedTs time.Time - FireTs time.Time - NextFireTs time.Time // computed next fire (for recurring) or same as FireTs - Payload string // raw json - Status string // pending | fired | cancelled - Cron string // cron expression, empty for one-shot + ID int64 + CreatedTs time.Time + FireTs time.Time + NextFireTs time.Time // computed next fire (for recurring) or same as FireTs + Payload string // raw json + Status string // pending | fired | cancelled + Cron string // cron expression, empty for one-shot // Collapsed — set only on a synthetic digest reminder (ID=0): the original // due reminders it stands in for. Not persisted. The dispatcher completes @@ -144,4 +144,4 @@ func (s *Store) RescheduleReminder(ctx context.Context, id int64, now time.Time) } _, err = s.db.ExecContext(ctx, "UPDATE reminders SET next_fire_ts = ? WHERE id = ?", next.UnixMilli(), id) return err -} \ No newline at end of file +} diff --git a/internal/store/store.go b/internal/store/store.go index beacac2..2131245 100644 --- a/internal/store/store.go +++ b/internal/store/store.go @@ -123,4 +123,4 @@ var ( ErrConfidence = errors.New("store: confidence must be in (0.0, 1.0]") // ErrVoidsMissing — a correction pointed at a nonexistent fact. ErrVoidsMissing = errors.New("store: voids_id does not reference an existing fact") -) \ No newline at end of file +) diff --git a/internal/store/store_test.go b/internal/store/store_test.go index 9e49052..3e915b2 100644 --- a/internal/store/store_test.go +++ b/internal/store/store_test.go @@ -377,4 +377,4 @@ func TestCalendarEvents(t *testing.T) { if len(events) != 0 { t.Fatalf("expected 0 events on July 8, got %d", len(events)) } -} \ No newline at end of file +} diff --git a/internal/stt/stt.go b/internal/stt/stt.go index 11cc107..805891f 100644 --- a/internal/stt/stt.go +++ b/internal/stt/stt.go @@ -62,12 +62,12 @@ func NewStub() *Stub { return &Stub{} } // classifier will route to a different intent (act / reminder / fact / note // / query). The hash picks the phrase per utterance deterministically. var stubPhrases = []string{ - "maven, отметь что я выпил воды", // fact (water tap → fact table) + "maven, отметь что я выпил воды", // fact (water tap → fact table) "maven, напомни через 4 часа размяться", // reminder (→ reminders table) - "maven, restart nginx", // act (→ stage-0 grammar hit) - "maven, что у меня сегодня по календарю", // query (→ slm read-path, deferred) + "maven, restart nginx", // act (→ stage-0 grammar hit) + "maven, что у меня сегодня по календарю", // query (→ slm read-path, deferred) "note: idea — staggered cooldown by time of day", // note (→ chroma, deferred) - "slept 6h, fan noise wrecked it", // compound capture (open spec; routes as fact today) + "slept 6h, fan noise wrecked it", // compound capture (open spec; routes as fact today) } // Transcribe returns one of stubPhrases, indexed by a hash of the audio @@ -88,7 +88,7 @@ func (s *Stub) Transcribe(_ context.Context, a audio.Audio) (string, float64, er // the stt module's unix socket. The daemon constructs one when its config // points at a worker socket; otherwise it uses the Stub. type Remote struct { - c *worker.Client + c *worker.Client lang string } @@ -109,4 +109,4 @@ func (r *Remote) Transcribe(ctx context.Context, a audio.Audio) (string, float64 return "", 0, fmt.Errorf("stt: transcribe: %w", err) } return resp.Text, resp.Confidence, nil -} \ No newline at end of file +} diff --git a/internal/stt/stt_test.go b/internal/stt/stt_test.go index 9e54146..823959b 100644 --- a/internal/stt/stt_test.go +++ b/internal/stt/stt_test.go @@ -122,4 +122,4 @@ func TestRemoteErrorWraps(t *testing.T) { if err == nil { t.Fatalf("want error, got nil") } -} \ No newline at end of file +} diff --git a/internal/tts/tts.go b/internal/tts/tts.go index 04f83a7..089e49a 100644 --- a/internal/tts/tts.go +++ b/internal/tts/tts.go @@ -68,8 +68,8 @@ func (s *Stub) Synthesize(_ context.Context, text string) (audio.Audio, error) { // Remote — the worker-backed Synthesizer. Holds a worker.Client that dials // the tts module's unix socket. type Remote struct { - c *worker.Client - lang string + c *worker.Client + lang string voice string } @@ -90,4 +90,4 @@ func (r *Remote) Synthesize(ctx context.Context, text string) (audio.Audio, erro return audio.Audio{}, fmt.Errorf("tts: synthesize: %w", err) } return resp.Audio, nil -} \ No newline at end of file +} diff --git a/internal/tts/tts_test.go b/internal/tts/tts_test.go index 1bc3e5d..c96ee25 100644 --- a/internal/tts/tts_test.go +++ b/internal/tts/tts_test.go @@ -135,4 +135,4 @@ func samePCM(a, b []byte) bool { } } return true -} \ No newline at end of file +} diff --git a/internal/voice/client.go b/internal/voice/client.go index 26ab47e..ca7260b 100644 --- a/internal/voice/client.go +++ b/internal/voice/client.go @@ -40,9 +40,9 @@ type PushHandler interface { // Client — one connection to the voice.Server. type Client struct { - addr string - mu sync.Mutex - c net.Conn + addr string + mu sync.Mutex + c net.Conn nextID atomic.Uint64 // pushCh fan-out: a reader goroutine (started by RunPushReceiver) @@ -237,4 +237,4 @@ func marshalParams(v any) (json.RawMessage, error) { return nil, fmt.Errorf("voice: marshal params: %w", err) } return b, nil -} \ No newline at end of file +} diff --git a/internal/voice/errors.go b/internal/voice/errors.go index 801e836..3ba0ab2 100644 --- a/internal/voice/errors.go +++ b/internal/voice/errors.go @@ -12,9 +12,9 @@ import ( // in-process and over-the-wire. var ( ErrUnknownMethod = errors.New("voice: unknown method") - ErrBadParams = errors.New("voice: bad params") - ErrForbidden = errors.New("voice: forbidden") - ErrNoSession = errors.New("voice: no live client session") // voicesink unable to push + ErrBadParams = errors.New("voice: bad params") + ErrForbidden = errors.New("voice: forbidden") + ErrNoSession = errors.New("voice: no live client session") // voicesink unable to push ) // Sentinel wire codes. Stable; do not rename. @@ -69,5 +69,5 @@ func hydrate(e *RpcError) error { } // json helpers kept local so call sites read clean. -func jsonMarshal(v any) ([]byte, error) { return json.Marshal(v) } -func jsonUnmarshal(b []byte, v any) error { return json.Unmarshal(b, v) } \ No newline at end of file +func jsonMarshal(v any) ([]byte, error) { return json.Marshal(v) } +func jsonUnmarshal(b []byte, v any) error { return json.Unmarshal(b, v) } diff --git a/internal/voice/frame.go b/internal/voice/frame.go index c556124..711c60b 100644 --- a/internal/voice/frame.go +++ b/internal/voice/frame.go @@ -57,4 +57,4 @@ func readFrame(r io.Reader, v any) error { return fmt.Errorf("voice: unmarshal frame: %w", err) } return nil -} \ No newline at end of file +} diff --git a/internal/voice/replier.go b/internal/voice/replier.go index cf4bb46..5d9e2b3 100644 --- a/internal/voice/replier.go +++ b/internal/voice/replier.go @@ -81,4 +81,4 @@ func (s *StubReplier) Reply(d router.Decision) string { default: return "приняла." } -} \ No newline at end of file +} diff --git a/internal/voice/server.go b/internal/voice/server.go index 7bbf111..8efd937 100644 --- a/internal/voice/server.go +++ b/internal/voice/server.go @@ -234,4 +234,4 @@ func marshalResult(v any) json.RawMessage { return b } -// io.EOF — used by serveConn to detect a quiet client disconnect. \ No newline at end of file +// io.EOF — used by serveConn to detect a quiet client disconnect. diff --git a/internal/voice/session.go b/internal/voice/session.go index f7e2f2a..c7b9eef 100644 --- a/internal/voice/session.go +++ b/internal/voice/session.go @@ -30,16 +30,16 @@ import ( // the registry reads lastActive via LastActive; the conn is closed when // the client disconnects (serveConn returns) or the server shuts down. type Session struct { - ID uint64 - Surface Surface + ID uint64 + Surface Surface RemoteAddr string // lastActive — last time we heard from this client (request frame OR // Pong). PickRecent selects the max-lastActive session for routing. lastActive atomic.Int64 // unix nano - mu sync.Mutex - conn net.Conn + mu sync.Mutex + conn net.Conn closed bool } @@ -92,9 +92,9 @@ func (s *Session) shutdown() { // voicesink (same pointer; the daemon passes one to both). Mutex around the // map; per-session conn writes use the session's own lock. type Sessions struct { - mu sync.Mutex - sess map[uint64]*Session - nextID uint64 + mu sync.Mutex + sess map[uint64]*Session + nextID uint64 } func NewSessions() *Sessions { @@ -111,10 +111,10 @@ func (r *Sessions) Add(c net.Conn, surface Surface) *Session { defer r.mu.Unlock() r.nextID++ s := &Session{ - ID: r.nextID, - Surface: surface, + ID: r.nextID, + Surface: surface, RemoteAddr: c.RemoteAddr().String(), - conn: c, + conn: c, } s.setLastActive(time.Now()) r.sess[s.ID] = s @@ -170,4 +170,3 @@ func (r *Sessions) PushToMostRecent(ctx context.Context, p AudioNudgePush) error } return best.pushAudio(p) } - diff --git a/internal/voice/voice_test.go b/internal/voice/voice_test.go index d5a5441..3483352 100644 --- a/internal/voice/voice_test.go +++ b/internal/voice/voice_test.go @@ -220,4 +220,4 @@ func TestClientListenModeReceivesPush(t *testing.T) { type pushHandlerFunc func(Push) -func (f pushHandlerFunc) OnPush(p Push) { f(p) } \ No newline at end of file +func (f pushHandlerFunc) OnPush(p Push) { f(p) } diff --git a/internal/voice/wire.go b/internal/voice/wire.go index b90e476..c83c4ec 100644 --- a/internal/voice/wire.go +++ b/internal/voice/wire.go @@ -61,12 +61,12 @@ type Surface = auth.Surface // internal/auth directly. The auth package IS the source of truth; these // aliases forward to it. const ( - SurfaceVoice = auth.SurfaceVoice - SurfaceTelegram = auth.SurfaceTelegram - SurfacePCClient = auth.SurfacePCClient - SurfaceAuthedPage = auth.SurfaceAuthedPage + SurfaceVoice = auth.SurfaceVoice + SurfaceTelegram = auth.SurfaceTelegram + SurfacePCClient = auth.SurfacePCClient + SurfaceAuthedPage = auth.SurfaceAuthedPage SurfaceCoreProcess = auth.SurfaceCoreProcess - SurfaceUnknown = auth.SurfaceUnknown + SurfaceUnknown = auth.SurfaceUnknown ) // maxFrame — 64 MiB. Same instinct as worker: a single utterance at 16k mono @@ -114,7 +114,7 @@ type Response struct { // awaiting a Request response is interleaved — the client distinguishes by // the json shape (Response has `id`, Push has `kind`). type Push struct { - Kind PushKind `json:"kind"` + Kind PushKind `json:"kind"` Params json.RawMessage `json:"p,omitempty"` } @@ -152,9 +152,9 @@ type RpcError struct { // every conn, but the wire carries it so future mTLS / passkey handshakes // can populate it without a protocol version bump. type PushToTalkReq struct { - Audio audio.Audio `json:"audio"` - Lang string `json:"lang,omitempty"` - Surface Surface `json:"surface,omitempty"` + Audio audio.Audio `json:"audio"` + Lang string `json:"lang,omitempty"` + Surface Surface `json:"surface,omitempty"` } // PushToTalkResp — the reply. ReplyAudio is TTS-synthesised; ReplyText is @@ -164,19 +164,19 @@ type PushToTalkReq struct { // nudge fired alongside the reply and was forwarded to ntfy); today the // reactive handler doesn't dispatch nudges, so this is empty. type PushToTalkResp struct { - ReplyAudio audio.Audio `json:"reply_audio"` - ReplyText string `json:"reply_text"` - Transcript string `json:"transcript,omitempty"` - RoutedChannels []string `json:"routed_channels,omitempty"` + ReplyAudio audio.Audio `json:"reply_audio"` + ReplyText string `json:"reply_text"` + Transcript string `json:"transcript,omitempty"` + RoutedChannels []string `json:"routed_channels,omitempty"` } // AudioNudgePush — the proactive nudge push payload. RuleName + Severity // for the client to display; Audio is the synthesised Body; Text is the // same in text form. type AudioNudgePush struct { - RuleName string `json:"rule_name"` - Severity int `json:"severity"` - Audio audio.Audio `json:"audio"` - Text string `json:"text"` - Ts time.Time `json:"ts"` -} \ No newline at end of file + RuleName string `json:"rule_name"` + Severity int `json:"severity"` + Audio audio.Audio `json:"audio"` + Text string `json:"text"` + Ts time.Time `json:"ts"` +} diff --git a/internal/webauthn/cbor.go b/internal/webauthn/cbor.go index 4aa07ae..7635ffd 100644 --- a/internal/webauthn/cbor.go +++ b/internal/webauthn/cbor.go @@ -240,5 +240,7 @@ func decodeArg(data []byte, off int, ai byte) (uint64, int, error) { } func readBE16(b []byte) uint16 { return uint16(b[0])<<8 | uint16(b[1]) } -func readBE32(b []byte) uint32 { return uint32(b[0])<<24 | uint32(b[1])<<16 | uint32(b[2])<<8 | uint32(b[3]) } +func readBE32(b []byte) uint32 { + return uint32(b[0])<<24 | uint32(b[1])<<16 | uint32(b[2])<<8 | uint32(b[3]) +} func readBE64(b []byte) uint64 { return uint64(readBE32(b))<<32 | uint64(readBE32(b[4:])) } diff --git a/internal/webauthn/webauthn.go b/internal/webauthn/webauthn.go index 4522be5..21d4fb8 100644 --- a/internal/webauthn/webauthn.go +++ b/internal/webauthn/webauthn.go @@ -121,9 +121,9 @@ func (rp *RP) CreationOptions(userID []byte, userName string) (map[string]any, s "pubKeyCredParams": []map[string]any{ {"type": "public-key", "alg": -7}, // ES256 }, - "timeout": 60000, - "attestation": "none", - "excludeCredentials": []any{}, + "timeout": 60000, + "attestation": "none", + "excludeCredentials": []any{}, }, challengeB64, nil } @@ -188,11 +188,11 @@ func (rp *RP) AssertionOptions() (map[string]any, string, error) { } return map[string]any{ - "challenge": challengeB64, - "timeout": 60000, - "rpId": rp.cfg.RPID, - "allowCredentials": []any{}, - "userVerification": "required", + "challenge": challengeB64, + "timeout": 60000, + "rpId": rp.cfg.RPID, + "allowCredentials": []any{}, + "userVerification": "required", }, challengeB64, nil } diff --git a/internal/worker/client.go b/internal/worker/client.go index 23e3a40..73b62ce 100644 --- a/internal/worker/client.go +++ b/internal/worker/client.go @@ -33,8 +33,8 @@ import ( type Client struct { path string - mu sync.Mutex - c net.Conn + mu sync.Mutex + c net.Conn dial func() (net.Conn, error) } @@ -188,4 +188,4 @@ func marshalParams(v any) (json.RawMessage, error) { return nil, fmt.Errorf("worker: marshal params: %w", err) } return b, nil -} \ No newline at end of file +} diff --git a/internal/worker/frame.go b/internal/worker/frame.go index 9e00a1e..e57e4e7 100644 --- a/internal/worker/frame.go +++ b/internal/worker/frame.go @@ -64,4 +64,4 @@ func readFrame(r io.Reader, v any) error { return fmt.Errorf("worker: unmarshal frame: %w", err) } return nil -} \ No newline at end of file +} diff --git a/internal/worker/handler.go b/internal/worker/handler.go index bcdd06e..ad6d27c 100644 --- a/internal/worker/handler.go +++ b/internal/worker/handler.go @@ -22,4 +22,4 @@ type Transcriber interface { // so a server asked for the wrong verb refuses cleanly). type Synthesizer interface { Synthesize(ctx context.Context, req SynthesizeReq) (SynthesizeResp, error) -} \ No newline at end of file +} diff --git a/internal/worker/jobs.go b/internal/worker/jobs.go index 00397ef..4169271 100644 --- a/internal/worker/jobs.go +++ b/internal/worker/jobs.go @@ -42,13 +42,13 @@ type TranscribeResp struct { // SynthesizeReq — the synthesize verb args. type SynthesizeReq struct { - Text string `json:"text"` - Lang string `json:"lang"` // "ru" | "en" | "" - Voice string `json:"voice"` // named voice or "" ⇒ worker default + Text string `json:"text"` + Lang string `json:"lang"` // "ru" | "en" | "" + Voice string `json:"voice"` // named voice or "" ⇒ worker default Speed float64 `json:"speed"` // 1.0 = normal; clamped server-side } // SynthesizeResp — the synthesize verb result. type SynthesizeResp struct { Audio audio.Audio `json:"audio"` // rendered PCM -} \ No newline at end of file +} diff --git a/internal/worker/server.go b/internal/worker/server.go index 2682fcc..b8f8f42 100644 --- a/internal/worker/server.go +++ b/internal/worker/server.go @@ -225,4 +225,4 @@ func parentDir(p string) string { } } return "." -} \ No newline at end of file +} diff --git a/internal/worker/wire.go b/internal/worker/wire.go index 01a614d..ef52b9c 100644 --- a/internal/worker/wire.go +++ b/internal/worker/wire.go @@ -62,7 +62,7 @@ type Request struct { // Response — one frame back from the worker. Exactly one of Result/Error is set. type Response struct { Result json.RawMessage `json:"r,omitempty"` - Error *RpcError `json:"e,omitempty"` + Error *RpcError `json:"e,omitempty"` } // RpcError — a typed wire error. Mirrors internal/ipc's shape for tooling @@ -120,4 +120,4 @@ func rpcErr(err error) *RpcError { return &RpcError{Code: c, Message: err.Error()} } return &RpcError{Code: c} -} \ No newline at end of file +} diff --git a/internal/worker/worker_test.go b/internal/worker/worker_test.go index 4ac4e77..b41ba9e 100644 --- a/internal/worker/worker_test.go +++ b/internal/worker/worker_test.go @@ -286,4 +286,4 @@ func TestPathAfterListen(t *testing.T) { func contains(haystack, needle string) bool { return len(haystack) >= len(needle) && (bytes.Contains([]byte(haystack), []byte(needle))) -} \ No newline at end of file +}