package ipc import ( "context" "time" ) // CoreAPI and the eight domain interfaces it composes (Vikunja #408). // // It used to be one flat block of forty methods, and the cost of that shape // was paid three times over: every method added an arm to the dispatcher, a // stub to the daemon's locked API, and a stub to every test double. Two of // those three are already gone — the dispatcher is a table (methodTable in // server.go), and UnimplementedCoreAPI took the padding out of the doubles and // out of lockedAPI, which no longer exists. // // What was left is the interface itself, and this file is that half. CoreAPI // is unchanged as a type: the same forty methods, in the same order, so the // wire contract, the client proxy and the store adapter are all untouched. // What it gains is a named seam per domain, so a caller that only reads facts // can say FactAPI and a reader can see which cluster a method belongs to // without counting lines. // // Add a method to the domain it belongs to, not to CoreAPI. // FactAPI — the fact store: write, read the current value, read history, and // undo. Presence sits here because it is a hysteresis view over presence // facts, not a store of its own. type FactAPI interface { WriteFact(ctx context.Context, req WriteFactReq) (int64, error) LatestFact(ctx context.Context, key string) (Fact, error) LatestFactBySource(ctx context.Context, key, source string) (Fact, error) Since(ctx context.Context, key string, now time.Time) (time.Duration, error) Presence(ctx context.Context) (Presence, error) RecentFacts(ctx context.Context, n int) ([]Fact, error) RecentActiveFactsByKind(ctx context.Context, kind string, n int) ([]Fact, error) CalendarEvents(ctx context.Context, from, to time.Time) ([]Fact, error) RevertFact(ctx context.Context, key string) (int64, error) } // ReminderAPI — scheduled sends the owner asked for. type ReminderAPI interface { CreateReminder(ctx context.Context, fire time.Time, payload, cron string) (int64, error) MarkReminder(ctx context.Context, id int64, status string) error ListReminders(ctx context.Context, n int) ([]Reminder, error) } // NudgeAPI — proactive sends Maven proposed, their outcomes, and the outbox // they went out through. type NudgeAPI interface { RecordNudge(ctx context.Context, rule, channel, message string, ts time.Time) (int64, error) ResolveNudge(ctx context.Context, id int64, outcome string, ts time.Time) error RecentOutcomes(ctx context.Context, rule string, n int) ([]string, error) RecentNudges(ctx context.Context, n int) ([]Nudge, error) // DeliveryAttempts reads the outbox, newest first. An empty status means // every status (Vikunja #390). DeliveryAttempts(ctx context.Context, status string, n int) ([]DeliveryAttempt, error) } // NoteAPI — free text he captured, plus the embedded recall over it. type NoteAPI interface { WriteNote(ctx context.Context, ts time.Time, text string, embedding []float32, source string) (int64, error) QueryNotes(ctx context.Context, embedding []float32, k int) ([]Note, error) RecentNotes(ctx context.Context, n int) ([]Note, error) // RecentNotesFromSource — the newest n notes whose source starts with // prefix. Notes Maven read rather than heard (rss:, crawl:) are excluded // from recall, so this is the only way to reach them, and it keeps the feed // answer from being crowded out of a fixed window by his own notes. RecentNotesFromSource(ctx context.Context, prefix string, n int) ([]Note, error) } // ToolAPI — the capability allowlist and its read side. // // ProposeTool drafts an inert 'proposed' tool scaffold (maven-callable); // returns whether a new proposal was written. EnableTool fills cmd + // destructive and flips status to 'enabled'. DisableTool reverts an // enabled tool back to proposed (it stays in the store, won't run). // Enable/DisableTool gate at AuthStepUp (allowlist mutation, human-only); // ProposeTool is maven-callable (no step-up — she has no passkey). // LookupTool/ListTools read them. // scope defaults to "homelab" when empty. type ToolAPI interface { ProposeTool(ctx context.Context, name, utterance, scope string, ts time.Time) (bool, error) EnableTool(ctx context.Context, name string, cmd []string, destructive bool, scope string, ts time.Time) error DisableTool(ctx context.Context, name string) error DeleteTool(ctx context.Context, name string) error LookupTool(ctx context.Context, name string) (Tool, error) ListTools(ctx context.Context, status string) ([]Tool, error) // MCPServers reports the configured MCP servers and their health // (Vikunja #251). Read-only introspection for /tools — there is no // "call this tool" method on purpose: an MCP tool runs through the same // allowlist, confirm turn and act path as any other tool, and a second // mutation path would be a second thing to get wrong. Empty when the // mcp config block is absent, which is the default. MCPServers(ctx context.Context) ([]MCPServerStatus, error) } // RoutineAPI — the shapes of his day: routines Maven noticed and proposed, the // morning checklist, and today's plan. // // MorningStatus and DayPlan are daemon-computed rather than stored, so the // store adapter returns an error for both — the same shape as TickTrace. type RoutineAPI interface { // ListProposedRoutines returns proposed routines, newest first. ListProposedRoutines(ctx context.Context) ([]ProposedRoutine, error) // DismissProposedRoutine flips a proposed routine to 'dismissed'. DismissProposedRoutine(ctx context.Context, id int64) error // AcceptProposedRoutine flips a proposed routine to 'accepted'. The tick // loop takes the schedule from there — no reminder is created (Vikunja #366). AcceptProposedRoutine(ctx context.Context, id int64) error // SeedEvent writes a backdated fact and runs extraction and detection over // it, so a proposal can be produced in one sitting rather than over real // days (Vikunja #518). See SeedEventReq for why this exists and what keeps // it shut. Daemon-computed, like MorningStatus — the store adapter refuses // it, because the detect-and-propose step lives in mavend. SeedEvent(ctx context.Context, req SeedEventReq) (SeedEventResp, error) // MorningStatus returns each configured morning routine's current // checklist state (see internal/morning): active today/now, which items // are done, which are still missing. MorningStatus(ctx context.Context) ([]MorningRoutineStatus, error) // DayPlan returns today's ordered plan — calendar events, pending // reminders and any morning checklist still outstanding (see // internal/morning.BuildPlan) — plus the spoken RU rendering of it. // Read-only: asking for the plan never dispatches or schedules anything. DayPlan(ctx context.Context) (DayPlan, error) } // TaskAPI — outstanding work, whatever surface it arrived from. type TaskAPI interface { // CaptureTask records a task. See CaptureTaskReq — this is the single // intake seam for the voice path, the web form and the future email // extractor. Idempotent per live normalised text; the response says // whether a row was actually created. CaptureTask(ctx context.Context, req CaptureTaskReq) (CaptureTaskResp, error) // ListTasks returns tasks in one status, newest first. "" is every row, // "live" is candidate + open (outstanding work). ListTasks(ctx context.Context, status string) ([]Task, error) // SetTaskStatus moves a task forward once: candidate→open|dropped, // open→done|dropped. Any other move is refused. SetTaskStatus(ctx context.Context, id int64, status string, ts time.Time, by string) error } // SystemAPI — what the daemon knows about itself, plus the one method that // runs a whole turn. type SystemAPI interface { // TickTrace returns the most recent tick's rule trace. The daemon caches // this after every tick; the store adapter returns an error (trace is not // persisted — it's a daemon-level cache). TickTrace(ctx context.Context) (TickTrace, error) // RecentEcosystemTraces reads the ecosystem call log, which lives in its // own table so machine-rate traces never crowd out human-rate facts. RecentEcosystemTraces(ctx context.Context, n int) ([]EcosystemTrace, error) // RecentEvents returns the daemon's unified intake journal, newest first // (Vikunja #283) — one envelope per thing that arrived, whatever direction // it came from: a relayed notification, a mail candidate, a feed item, a // changed page, a spend, a presence probe. // // Read-only and daemon-cached, the same shape as TickTrace and DayPlan: // the store adapter returns an error, because the journal is a bounded // in-memory ring and not a table. Its contents are a window over intake, // never the durable record — that is still the fact, note or task the // intake path wrote. RecentEvents(ctx context.Context, n int) ([]IntakeEvent, error) // Chat routes a text utterance through the reactive handler's core path // (router → dialogue → action → replier) and returns the reply text. // No audio or stt/tts — for text channels (mavweb, telegram). // // conversation names the thread. A parked clarifying question is held per // conversation, so an unanswered question on one reach cannot eat the next // utterance from another (Vikunja #466). Empty means the unattributed text // tap and is still one conversation of its own, separate from the mic. Chat(ctx context.Context, conversation, text string) (ChatReply, error) } // CoreAPI — what core exposes to modules. One Go interface, satisfied by: // - the in-process store adapter (storeapi.go) — used by the daemon // for modules that live in-process for now (router, delivery) and by tests, // - the socket-backed server's dispatcher (which delegates to a CoreAPI), // - the client proxy (client.go) — same interface, over the wire. // // So a module imports ipc, holds a CoreAPI, and is agnostic to whether it's // been wired in-process (tests / daemon-embedded) or socketed (full topology). // That swappability is the seam the auth layer inserts into without touching // the module code. type CoreAPI interface { FactAPI ReminderAPI NudgeAPI NoteAPI ToolAPI RoutineAPI TaskAPI SystemAPI }