kc

package module
v0.1.6 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 16, 2026 License: MIT Imports: 43 Imported by: 0

README

kite-mcp-kc

Go Reference

Core MCP orchestration for the algo2go/kite-mcp-* module family. Hosts the Manager composition root that owns per-user Kite sessions, the 5 Tier-1 facade services (stores, eventing, brokers, scheduling, session lifecycle), the 7 hexagonal-architecture port interfaces in kc/ports, the operations runbook handlers in kc/ops, and the CQRS command/query bus registrar. Largest module in the family by LOC (~18K production, ~37K test).

Status

v0.1.3 — production. Active development; functional-option API stable. Consumed via GOPROXY by kite-mcp-bootstrap (composition root) and kite-mcp-tools-common (typed Provider ports in ToolHandlerDeps).

Install

go get github.com/algo2go/kite-mcp-kc@v0.1.3

Quick start

import (
    "context"

    "github.com/algo2go/kite-mcp-kc"
    "github.com/algo2go/kite-mcp-metrics"
)

// Functional-options constructor (preferred).
mgr, err := kc.NewWithOptions(context.Background(),
    kc.WithKiteCredentials(apiKey, apiSecret),
    kc.WithLogger(slogLogger),
    kc.WithExternalURL("https://example.fly.dev"),
    kc.WithAlertDBPath("/data/alerts.db"),
    kc.WithEncryptionSecret(jwtSecret),
    kc.WithMetrics(metrics.New(metrics.Config{Logger: slogLogger})),
    kc.WithAdminSecretPath("/admin/ops"),
)
if err != nil {
    log.Fatal(err)
}
defer mgr.Shutdown(context.Background())

// Per-user session lifecycle
sessionID := mgr.GenerateSession()
loginURL  := mgr.LoginURL(sessionID, "user@example.com")
// ... user completes Kite OAuth → /callback hits mgr.CompleteSession(...)

// Read-side access (used by every MCP tool body)
brokerClient, ok := mgr.GetBrokerForEmail(email)

// Wire the per-domain register methods into the MCP server registrations
// (account / admin / alerts / exit / MF / native-alerts / OAuth-bridge /
//  orders / setup / ticker). See bootstrap for full wiring.

Public API

Root package kc
  • kc.Manager — composition root. ~91 exported methods spanning store accessors (AlertStore, AuditStore, BillingStore, CredentialStore, EventStoreConcrete, …), broker registry (Brokers, GetBrokerForEmail), session lifecycle (GenerateSession, CompleteSession, CompleteSessionAndRotate, ClearSession, CleanupExpiredSessions, GetActiveSessionCount), credential surface (APIKey, GetAPIKeyForEmail, GetAPISecretForEmail, GetAccessTokenForEmail), config (AdminSecretPath, DevMode, ExternalURL), CQRS (CommandBus), eventing (Eventing, EventDispatcher), and instruments (ForceInstrumentsUpdate).
  • Constructors — three patterns:
    • NewWithOptions(ctx, opts...) — preferred; functional options.
    • New(cfg Config) — config-struct constructor.
    • NewManager(apiKey, apiSecret, logger) — minimal-credentials helper for tests and tiny embedders.
  • kc.Option — 19 functional-option builders: WithConfig, WithContext, WithLogger, WithKiteCredentials, WithAccessToken, WithMetrics, WithTelegramBotToken, WithAlertDBPath, WithAppMode, WithExternalURL, WithAdminSecretPath, WithEncryptionSecret, WithDevMode, WithInstrumentsManager, WithInstrumentsConfig, WithInstrumentsSkipFetch, WithSessionSigner, WithBotFactory.
  • kc.Config — flat config struct (used by New + composed by WithConfig).
  • Per-domain registration methods on *Manager invoked by the bootstrap's tool-registry init sequence: RegisterAccount, RegisterAdmin, RegisterAlerts, RegisterExit, RegisterMF, RegisterNativeAlerts, RegisterOAuthBridge, RegisterOrders, RegisterSetup, RegisterTicker.
Sub-package kc/ports

Hexagonal-architecture port interfaces. Every Provider here narrows the god-object *kc.Manager to the minimum surface a tool handler actually needs — the Sprint 5 typed-deps architectural payoff. Seven interfaces:

  • ports.SessionPort — session lifecycle (create, lookup, complete, rotate, clear, cleanup, active-count).
  • ports.CredentialPort — per-user Kite-credential surface (GetAPIKey, GetAPISecret, GetAccessToken, encrypted store CRUD).
  • ports.AlertPort — alert-store read/write.
  • ports.OrderPort — broker-order read/write narrowed to the fields a tool handler needs (no full gokiteconnect.Client).
  • ports.InstrumentPort — instrument-master lookups + freeze-qty.
  • ports.AuditStoreConcreteProvider — concrete audit-store reach for the Phase 3 ops sub-git.
  • ports.SessionRegistryProvider — concrete session-registry reach for the same sub-git.

Leaf-stability invariant: kc/ports must NOT import kc parent. Enforced by kc/ports/leaf_stability_test.go (compile-time test). Inverse direction (kc/ports/assertions.go declaring var _ SessionPort = (*kc.Manager)(nil)) is intentionally allowed; the one-way graph is what makes the port abstraction work.

Sub-package kc/ops

Operations runbook handlers — admin endpoints + user-account self-service flows. Sub-packages:

  • ops/admin — admin-role-gated endpoints (user listing, session introspection, kill switches, metrics).
  • ops/shared — handler primitives reused by both admin + user.
  • ops/user — per-user self-service (credential rotation, alert CRUD, paper-portfolio reset).
Sub-package kc/internal

Implementation details NOT part of the public contract. Pinned reserve for utility code that should never escape the module boundary (see kc/internal/util/).

Used by

  • algo2go/kite-mcp-bootstrap — composition root; constructs the *kc.Manager via NewWithOptions and wires it through the per-domain Register methods into the HTTP mux + MCP tool registry.
  • algo2go/kite-mcp-tools-common — the typed Provider ports in common/handler_deps.go reference kc/ports.* interfaces by name; the legacy Tool.Handler(*kc.Manager) signature also keeps a direct *kc.Manager reference during the Sprint 5 migration window.

Design

Origin: extracted 2026-05-16 from github.com/algo2go/kite-mcp-bootstrap/kc as Phase 1 of the bootstrap-decomposition arc (Shape A* in the strategy doc at kite-mcp-server/.research/bootstrap-decomp-strategy.md, commit 280ae67). Zero behavior change at extraction.

Why kc is the composition root: every other algo2go/kite-mcp-* module is either a leaf (clockport, metrics, isttz, sectors, money, i18n, legaldocs, logger, templates, aop, domain) or a bounded-context service (alerts, audit, billing, broker, riskguard, oauth, papertrading, usecases, scheduler, telegram, users, watchlist, instruments, ticker, registry, cqrs, eventsourcing). kc binds them into one orchestrated unit via Manager — the only place that references most of the others simultaneously. This is the "monolith" that the decomposition arc is actively chipping down: every kc method that can be pushed into a per-domain service object reduces kc's god-object weight.

Tier-1 facade services owned by Manager:

  1. Stores facade — alert / audit / billing / credential / event / instrument / watchlist / user / session-registry stores.
  2. Eventing facadeEventDispatcher + Eventing accessor for domain-event publication.
  3. Brokers facade — per-user broker-client registry (gokiteconnect-backed; one client cached per authenticated user).
  4. Scheduling facade — periodic-task scheduler (token-rotation, alert-evaluation, instrument-update polls).
  5. Session lifecycle facadeGenerateSession, CompleteSession, CompleteSessionAndRotate, ClearSession, CleanupExpiredSessions.

Seven focused service objects (Sprint 5 cohesion target): CredentialService, SessionService, ManagedSessionService, PortfolioService, OrderService, AlertService, FamilyService. Each is a narrow surface on top of the stores + brokers facades; the Sprint 5 / Tier B Step 2 refactor (commit 0534edd) folded 13 Wave D use-case fields into OrderService as the most recent cohesion pass.

Related modules:

  • algo2go/kite-mcp-clockportClock port for deterministic scheduler tests.
  • algo2go/kite-mcp-metrics — telemetry; mounted via WithMetrics.
  • algo2go/kite-mcp-broker — broker port interface; gokiteconnect adapter wraps it.
  • algo2go/kite-mcp-tools-common — narrows kc's surface to the typed Provider ports the tool handlers actually need.

Contributing

Issues + PRs at github.com/algo2go/kite-mcp-kc. MIT licensed.

License

MIT — see LICENSE.

Documentation

Overview

Package kc provides store interfaces for hexagonal architecture.

Each interface defines the contract that other packages depend on, allowing concrete implementations to be swapped (e.g., in-memory vs. SQLite vs. mock). Compile-time verification ensures the concrete types satisfy these interfaces.

Package kc — manager.go: Manager constructors only.

Anchor 6 PR 6.15 collapsed manager.go to the constructor surface. The Manager struct + sentinel errors + KiteSessionData alias moved to kc/manager_struct.go; Config moved to kc/config.go; KiteConnect moved to kc/kite_connect.go; truncKey moved to kc/internal/util/ (exported as util.Trunc at v0.1.1). The remaining pointers (// X lives in Y) are listed at the bottom of the file as a roadmap for new contributors.

Index

Constants

View Source
const (
	HintClaudeDesktop = "claude-desktop"
	HintClaudeCode    = "claude-code"
	HintClaudeWeb     = "claude-web"
	HintCursor        = "cursor"
	HintChatGPT       = "chatgpt"
	HintVSCode        = "vscode"
	HintMCPRemote     = "mcp-remote"
	HintUnknown       = "unknown"
)

Known normalized hints. Keep this list in sync with the docstring on MCPSession.ClientHint and with any UI that filters/sums by hint.

View Source
const (
	// Default session configuration
	DefaultSessionDuration = 12 * time.Hour
	DefaultCleanupInterval = 30 * time.Minute
)
View Source
const (
	// Default expiry for signed session parameters (30 minutes)
	DefaultSignatureExpiry = 30 * time.Minute

	// Maximum allowed clock skew for signature validation
	MaxClockSkew = 5 * time.Minute
)

Variables

View Source
var (
	ErrSessionNotFound  = errors.New("MCP session not found or Kite session not associated, try to login again")
	ErrInvalidSessionID = errors.New("invalid MCP session ID, please try logging in again")
)
View Source
var (
	ErrInvalidSignature = errors.New("invalid session signature")
	ErrTamperedSession  = errors.New("session parameter has been tampered with")
	ErrExpiredSignature = errors.New("session signature has expired")
	ErrInvalidFormat    = errors.New("invalid session parameter format")
)
View Source
var ErrEmptySecretKey = errors.New("secret key cannot be empty")

ErrEmptySecretKey is returned when an empty secret key is provided.

View Source
var KolkataLocation = isttz.Location

KolkataLocation is the Asia/Kolkata timezone used throughout for IST operations. Delegates to kc/isttz which is a leaf package importable from anywhere.

Functions

func ClientHintFromRequest

func ClientHintFromRequest(r *http.Request) string

ClientHintFromRequest extracts a normalized client hint from an HTTP request. Returns an empty string when the request is nil (e.g. when called from the mcp-go idle sweeper path), letting the caller fall back to its own default behavior.

When r is non-nil, this function always returns a non-empty hint (the normalized string "unknown" if no headers match).

func DetectClientHint

func DetectClientHint(userAgent, oauthClientName, mcpClientInfoName string) string

DetectClientHint returns a normalized hint based on the three signals available at session creation time. See the package comment above for priority order.

Behavior:

  • Tries userAgent first. If it matches a known pattern, that wins.
  • Falls through to oauthClientName, then mcpClientInfoName.
  • When nothing matches (or all inputs are empty), returns HintUnknown.

The function is deliberately permissive — it returns a value for every input, including empty strings, rather than signalling errors. Sessions without an identifiable client are reported as "unknown" rather than blocking the session from being created.

func IsKiteTokenExpired

func IsKiteTokenExpired(storedAt time.Time) bool

IsKiteTokenExpired checks if a Kite token stored at the given time has likely expired. Kite tokens expire daily around 6 AM IST.

Retained as the free-standing helper for callers that only have a storedAt timestamp (no email/token fields). For callers with a full KiteTokenEntry, prefer ToDomainSession(email, entry).IsExpired().

func ToDomainSession

func ToDomainSession(email string, entry *KiteTokenEntry) domain.Session

ToDomainSession converts a KiteTokenEntry (+ email) into the rich domain Session entity. Converter boundary between the kc infrastructure type and the domain entity — kc retains zero knowledge of domain method bodies.

Types

type AdminAlertsRegistrarDeps

type AdminAlertsRegistrarDeps struct {
	AlertStore        *alerts.Store
	InstrumentsGetter func() *instruments.Manager // for adminBatchInstrumentResolver
	DispatcherGetter  func() *domain.EventDispatcher
	EventStoreGetter  func() *eventsourcing.EventStore
}

type AdminMFRegistrarDeps

type AdminMFRegistrarDeps struct {
	SessionSvc       *SessionService
	DispatcherGetter func() *domain.EventDispatcher
	EventStoreGetter func() *eventsourcing.EventStore
}

type AdminNativeAlertsRegistrarDeps

type AdminNativeAlertsRegistrarDeps struct {
	SessionSvc       *SessionService
	DispatcherGetter func() *domain.EventDispatcher
	EventStoreGetter func() *eventsourcing.EventStore
}

AdminNativeAlertsRegistrarDeps holds the dependencies for native-alert admin commands (Place/Modify/Delete; 3 commands).

type AdminRiskRegistrarDeps

type AdminRiskRegistrarDeps struct {
	RiskGuardGetter func() *riskguard.Guard // required at command-dispatch time
}

type AdminTickerRegistrarDeps

type AdminTickerRegistrarDeps struct {
	TickerServiceGetter func() *ticker.Service // required at command-dispatch time
}

type AdminUserRegistrarDeps

type AdminUserRegistrarDeps struct {
	UserStore        *users.Store
	RiskGuardGetter  func() *riskguard.Guard // may return nil; handler nil-safes
	SessionManager   *SessionRegistry        // may be nil at very-minimal fixtures
	DispatcherGetter func() *domain.EventDispatcher
}

AdminUserRegistrarDeps holds the dependencies for the user-lifecycle admin command handlers (suspend/activate/change-role). All deps default to closure-getters per the Tier 2.2 lesson (preserve laziness semantics at fixture-incomplete tests; eager dereference at registration time can change panic-reachability profiles).

type AlertService

type AlertService struct {
	// contains filtered or unexported fields
}

AlertService owns alert lifecycle: CRUD, evaluation, trailing stops, Telegram notifications, P&L snapshots, AND alert-DB lifecycle. Extracted from Manager as part of Clean Architecture / SOLID refactoring (Tier B Step 3 — alert subsystem bundle).

Tier B Step 3 absorbed the 6 raw alert fields that previously lived on Manager directly: alertStore, alertEvaluator, trailingStopMgr, telegramNotifier, alertDB, ownsAlertDB. AlertService is now the single source of truth for these; Manager keeps only a *AlertService pointer plus thin delegator methods on the Manager surface for backward compatibility with the ~60 call sites that read these fields.

Field-write protocol during Manager.NewWithOptions:

newEmptyManager constructs an empty AlertService up front. The init*
phases (initAlertSystem, initPersistence, initTelegramNotifier,
initAlertEvaluator, initTrailingStop) write into m.AlertSvc.<field>
directly using same-package field access. After initFocusedServices
the service is fully populated; consumer code reads via the accessor
methods on either *AlertService or *Manager.

Lifecycle invariant preserved verbatim: Manager.Shutdown closes the alert DB iff ownsAlertDB == true (manager opened it via OpenDB rather than receiving it from cfg.AlertDB).

func NewAlertService

func NewAlertService(cfg AlertServiceConfig) *AlertService

NewAlertService creates an AlertService from a pre-built dependency bundle. Retained for backward compatibility with the test sites in service_test.go.

func NewEmptyAlertService added in v0.1.4

func NewEmptyAlertService() *AlertService

NewEmptyAlertService allocates an AlertService with all fields zero. Used by newEmptyManager so the init* phases can populate fields in-place via same-package field access. Mirror of Step 2's OrderService pattern where the service is constructed before dependencies are wired.

func (*AlertService) AlertDB added in v0.1.4

func (as *AlertService) AlertDB() *alerts.DB

AlertDB returns the optional SQLite database used for persistence (nil when persistence is disabled or no DB path was configured).

func (*AlertService) AlertEvaluator

func (as *AlertService) AlertEvaluator() *alerts.Evaluator

AlertEvaluator returns the tick-to-alert matcher.

func (*AlertService) AlertStore

func (as *AlertService) AlertStore() *alerts.Store

AlertStore returns the per-user alert store (alert CRUD).

func (*AlertService) OwnsAlertDB added in v0.1.4

func (as *AlertService) OwnsAlertDB() bool

OwnsAlertDB reports whether this manager opened the alert DB itself (and therefore must Close it on shutdown). Returns false when the DB was supplied externally via Config.AlertDB.

func (*AlertService) PnLService

func (as *AlertService) PnLService() *alerts.PnLSnapshotService

PnLService returns the P&L snapshot service (nil if not initialized).

func (*AlertService) SetPnLService

func (as *AlertService) SetPnLService(svc *alerts.PnLSnapshotService)

SetPnLService sets the P&L snapshot service (called from app layer after initialization).

func (*AlertService) TelegramNotifier

func (as *AlertService) TelegramNotifier() *alerts.TelegramNotifier

TelegramNotifier returns the Telegram alert sender (nil if not configured).

func (*AlertService) TrailingStopManager

func (as *AlertService) TrailingStopManager() *alerts.TrailingStopManager

TrailingStopManager returns the trailing stop-loss manager.

type AlertServiceConfig

type AlertServiceConfig struct {
	AlertStore       *alerts.Store
	AlertEvaluator   *alerts.Evaluator
	TrailingStopMgr  *alerts.TrailingStopManager
	TelegramNotifier *alerts.TelegramNotifier
}

AlertServiceConfig holds dependencies for creating an AlertService via the legacy NewAlertService path. Retained for the two tests in service_test.go that construct an AlertService directly; production wiring uses NewEmptyAlertService + same-package field writes during the manager init phases.

type AlertStoreInterface

type AlertStoreInterface = alerts.AlertStoreInterface

AlertStoreInterface is the per-user price-alert interface. Anchor 5 PR 5.2 relocated the canonical declaration to kc/alerts/store_interface.go (its owning package); this alias preserves the legacy `kc.AlertStoreInterface` reference path so the 11+ pre-existing reverse-dep call sites build unchanged. Type aliases are not new types — `kc.AlertStoreInterface` and `alerts.AlertStoreInterface` are interchangeable at every call site, including the compile-time satisfaction check below (`_ AlertStoreInterface = (*alerts.Store)(nil)`).

Wave B-2 PR 5.3 will rewrite kc/ports/alert.go to reference `alerts.AlertStoreInterface` directly so it can drop its kc-parent import; this alias remains as the long-tail backward-compatibility shim until call sites migrate.

type AppConfigProvider

type AppConfigProvider interface {
	// IsLocalMode returns true when running in STDIO mode.
	IsLocalMode() bool

	// DevMode returns true when the server runs with a mock broker.
	DevMode() bool

	// ExternalURL returns the configured external URL.
	ExternalURL() string

	// AdminSecretPath returns the configured admin secret path.
	AdminSecretPath() string

	// APIKey returns the global Kite API key.
	APIKey() string
}

AppConfigProvider provides application-level configuration.

type AuditReader

type AuditReader interface {
	// List retrieves tool call records for a given email with filtering/pagination.
	List(email string, opts audit.ListOptions) ([]*audit.ToolCall, int, error)

	// ListOrders returns tool calls with order IDs for the given email.
	ListOrders(email string, since time.Time) ([]*audit.ToolCall, error)

	// GetOrderAttribution returns the decision trace for a given order.
	GetOrderAttribution(email, orderID string) ([]*audit.ToolCall, error)

	// GetStats returns aggregate stats for a given email since the given time.
	// Optional category and errorsOnly filters scope the results.
	GetStats(email string, since time.Time, category string, errorsOnly bool) (*audit.Stats, error)

	// GetToolCounts returns tool_name -> count for the given email.
	// Optional category and errorsOnly filters scope the results.
	GetToolCounts(email string, since time.Time, category string, errorsOnly bool) (map[string]int, error)

	// GetToolMetrics returns per-tool aggregate metrics since the given time.
	GetToolMetrics(since time.Time) ([]audit.ToolMetric, error)

	// GetGlobalStats returns aggregate stats across all users.
	GetGlobalStats(since time.Time) (*audit.Stats, error)

	// GetTopErrorUsers returns the top N users with the most errors since the given time.
	GetTopErrorUsers(since time.Time, limit int) ([]audit.UserErrorCount, error)

	// VerifyChain walks the hash chain and checks integrity.
	VerifyChain() (*audit.ChainVerification, error)
}

AuditReader provides read and query operations for audit records.

type AuditStoreInterface

type AuditStoreInterface interface {
	AuditWriter
	AuditReader
	AuditStreamer
}

AuditStoreInterface defines operations for recording and querying MCP tool call audit records. Composed from focused sub-interfaces.

type AuditStoreProvider

type AuditStoreProvider interface {
	AuditStore() AuditStoreInterface
}

AuditStoreProvider exposes the audit trail store. Returns nil if disabled.

type AuditStreamer

type AuditStreamer interface {
	// AddActivityListener registers a listener for real-time activity streaming.
	AddActivityListener(id string) chan *audit.ToolCall

	// RemoveActivityListener unregisters and closes a listener.
	RemoveActivityListener(id string)
}

AuditStreamer provides real-time activity streaming via listeners.

type AuditWriter

type AuditWriter interface {
	// EnqueueCtx adds a tool call to the async write buffer with a
	// request context for trace correlation. SOLID 99→100 cleanup
	// retired the legacy non-ctx Enqueue shim; consumers must thread
	// ctx (or context.Background() for service-ctx callbacks).
	EnqueueCtx(ctx context.Context, entry *audit.ToolCall)

	// Record inserts a tool call synchronously.
	Record(entry *audit.ToolCall) error

	// DeleteOlderThan removes tool_calls older than the given time.
	DeleteOlderThan(before time.Time) (int64, error)
}

AuditWriter provides write operations for audit records.

type BillingStoreInterface

type BillingStoreInterface interface {
	// GetTier returns the current billing tier for an email.
	GetTier(email string) billing.Tier

	// SetSubscription creates or updates a subscription.
	SetSubscription(sub *billing.Subscription) error

	// GetSubscription returns the subscription for an email, or nil.
	GetSubscription(email string) *billing.Subscription

	// GetEmailByCustomerID returns the email for a Stripe customer ID.
	GetEmailByCustomerID(customerID string) string

	// IsEventProcessed returns true if a webhook event has been handled.
	IsEventProcessed(eventID string) bool

	// MarkEventProcessed records that an event has been processed.
	MarkEventProcessed(eventID, eventType string) error

	// GetTierForUser returns the tier checking both direct subscription and admin linkage.
	GetTierForUser(email string, adminEmailFn func(string) string) billing.Tier
}

BillingStoreInterface defines operations for managing user billing subscriptions and tier enforcement.

type BillingStoreProvider

type BillingStoreProvider interface {
	BillingStore() BillingStoreInterface
}

BillingStoreProvider exposes the billing store. Returns nil if disabled.

type BrokerResolverProvider

type BrokerResolverProvider interface {
	GetBrokerForEmail(email string) (broker.Client, error)
	HasBrokerFactory() bool
}

BrokerResolverProvider exposes the broker-resolution surface that use cases and HTTP handlers need without forcing callers to reach for the full *SessionService. Anchor 6 PR 6.4 (per .research/anchor- 6-pr-6-4-broker-resolver-redesign.md commit a2a11db) narrowed the interface from a single SessionSvc() *SessionService method to the two methods consumers actually use:

  • GetBrokerForEmail (4 callsites: mcp/ext_apps.go, kc/manager_commands_admin.go, app/wire.go via the FillWatcherResolverFromBroker constructor, plus in-package CQRS use-case constructors via this interface)
  • HasBrokerFactory (1 callsite: app/http.go's auth-gate guard)

The narrower interface is satisfied by both *kc.SessionService (its existing methods) AND *kc.Manager (via the passthrough methods declared in kc/manager_accessors.go below). This dual- satisfaction lets PR 6.4 delete the Manager.SessionSvc() accessor while preserving the use-case-level BrokerResolver contract that kc/usecases/ports.go declares (one-method narrower port).

type BrokerServices

type BrokerServices struct {
	// contains filtered or unexported fields
}

BrokerServices groups broker-adjacent factories and subsystems: the Kite client factory, instruments manager, ticker service, paper trading engine, and risk guard. These previously lived as loose accessors on Manager.

Tier 1.1 (Path A.28 — kc-manager decomp design Tier-1.1): the back-pointer to *Manager has been replaced with closures over the underlying fields. Each method captures exactly the getter/setter pair it needs at construction time. Two consequences:

  1. The closures read the *current* field value at call time (the Manager-mutation-after-construction pattern still works), preserving observable behaviour identical to the prior `b.m.X` access.
  2. The struct no longer references *Manager at all, eliminating the cyclic-pointer architecture that the kc/ports invariant (assertions.go: "only ports imports kc, keeping the graph acyclic") would otherwise extend by example.

Empirically: 0 consumer signature changes outside this file. All external callers reach broker concerns via Manager-level delegators (m.RiskGuard, m.TickerService, etc.) which still exist below at lines covering "Manager-level delegators (moved from manager.go)". The delegators now invoke b.<method>() instead of b.m.<field>; same observable result.

func (*BrokerServices) ForceInstrumentsUpdate

func (b *BrokerServices) ForceInstrumentsUpdate() error

ForceInstrumentsUpdate forces an immediate instruments update.

func (*BrokerServices) GetInstrumentsStats

func (b *BrokerServices) GetInstrumentsStats() instruments.UpdateStats

GetInstrumentsStats returns current instruments update statistics.

func (*BrokerServices) InstrumentsManager

func (b *BrokerServices) InstrumentsManager() InstrumentManagerInterface

InstrumentsManager returns the instruments manager.

func (*BrokerServices) InstrumentsManagerConcrete

func (b *BrokerServices) InstrumentsManagerConcrete() *instruments.Manager

InstrumentsManagerConcrete returns the concrete instruments manager.

func (*BrokerServices) KiteClientFactory

func (b *BrokerServices) KiteClientFactory() KiteClientFactory

KiteClientFactory returns the factory used to create zerodha.KiteSDK instances.

func (*BrokerServices) PaperEngine

func (b *BrokerServices) PaperEngine() PaperEngineInterface

PaperEngine returns the paper trading engine, or nil if not configured.

func (*BrokerServices) PaperEngineConcrete

func (b *BrokerServices) PaperEngineConcrete() *papertrading.PaperEngine

PaperEngineConcrete returns the concrete paper engine.

func (*BrokerServices) RiskGuard

func (b *BrokerServices) RiskGuard() *riskguard.Guard

RiskGuard returns the riskguard instance, or nil if not configured.

func (*BrokerServices) SetKiteClientFactory

func (b *BrokerServices) SetKiteClientFactory(f KiteClientFactory)

SetKiteClientFactory overrides the default factory. Intended for tests.

func (*BrokerServices) SetPaperEngine

func (b *BrokerServices) SetPaperEngine(e *papertrading.PaperEngine)

SetPaperEngine sets the paper trading engine.

func (*BrokerServices) SetRiskGuard

func (b *BrokerServices) SetRiskGuard(guard *riskguard.Guard)

SetRiskGuard sets the riskguard for financial safety controls.

func (*BrokerServices) TickerService

func (b *BrokerServices) TickerService() TickerServiceInterface

TickerService returns the per-user WebSocket ticker service.

func (*BrokerServices) TickerServiceConcrete

func (b *BrokerServices) TickerServiceConcrete() *ticker.Service

TickerServiceConcrete returns the concrete ticker service.

func (*BrokerServices) UpdateInstrumentsConfig

func (b *BrokerServices) UpdateInstrumentsConfig(config *instruments.UpdateConfig)

UpdateInstrumentsConfig updates the instruments manager configuration.

type BrowserOpener

type BrowserOpener interface {
	OpenBrowser(rawURL string) error
}

BrowserOpener exposes the local-mode browser-open helper. Phase 3a Batch 2 narrow port for setup_tools.go (auto-open Kite login URL + dashboard URL on local STDIO runs). Hosted Fly.io callers depend on this being a no-op or returning an error — production safety.

type CleanupHook

type CleanupHook func(session *MCPSession)

CleanupHook is called when a session is terminated or expires

type ClientHintResolver

type ClientHintResolver struct {
	// contains filtered or unexported fields
}

ClientHintResolver is a SessionIdManagerResolver that derives a ClientHint from each incoming HTTP request and records it on the MCPSession the library is about to generate.

func NewClientHintResolver

func NewClientHintResolver(registry *SessionRegistry) *ClientHintResolver

NewClientHintResolver constructs a resolver backed by the given registry.

func (*ClientHintResolver) ResolveSessionIdManager

func (r *ClientHintResolver) ResolveSessionIdManager(req *http.Request) SessionIdManagerShim

ResolveSessionIdManager is the mcp-go hook. It inspects the request to determine the client hint, then returns a request-scoped wrapper around the underlying SessionRegistry.

Nil r is handled — the mcp-go idle-TTL sweeper passes a nil request, in which case we return the bare registry (Generate-path will produce an empty hint, which renders as "Unknown").

type CommandBusProvider

type CommandBusProvider interface {
	CommandBus() *cqrs.InMemoryBus
}

CommandBusProvider exposes the CQRS command bus for write dispatches. Handlers depend on this narrow port rather than pulling the full *Manager to reach CommandBus() — closes the last composition-root leak for write-side bus consumers.

type Config

type Config struct {
	APIKey             string                    // required
	APISecret          string                    // required
	AccessToken        string                    // optional: pre-set access token to bypass browser login
	Logger             *slog.Logger              // required
	InstrumentsConfig  *instruments.UpdateConfig // optional - defaults to instruments.DefaultUpdateConfig()
	InstrumentsManager *instruments.Manager      // optional - if provided, skips creating new instruments manager
	SessionSigner      *SessionSigner            // optional - if nil, creates new session signer
	Metrics            *metrics.Manager          // optional - for tracking user metrics
	TelegramBotToken   string                    // optional - for Telegram price alert notifications
	AlertDBPath        string                    // optional - SQLite path for alert persistence
	AppMode            string                    // optional - "stdio", "http", "sse"
	ExternalURL        string                    // optional - e.g. "https://kite-mcp-server.fly.dev"
	AdminSecretPath    string                    // optional - admin endpoint secret for ops dashboard URL
	EncryptionSecret   string                    // optional - secret for encrypting credentials at rest (typically OAUTH_JWT_SECRET)
	DevMode            bool                      // optional - use mock broker, no real Kite login required
	// InstrumentsSkipFetch causes the auto-created instruments manager to
	// skip the HTTP prefetch of api.kite.trade/instruments.json and load an
	// empty instrument map instead. Intended for tests that exercise the
	// full wiring (initializeServices) but do not need live instrument data
	// — isolates the test suite from external-API rate limits / outages.
	// Ignored when InstrumentsManager is already provided.
	InstrumentsSkipFetch bool

	// BotFactory is an optional per-Manager Telegram bot factory. When
	// non-nil, alerts.NewTelegramNotifierWithFactory is used to construct
	// the notifier — bypassing the kc/alerts package-level newBotFunc
	// global. Tests pass a fake-server-backed factory here to avoid the
	// global-mutex OverrideNewBotFunc pattern, unblocking t.Parallel.
	// Production wiring leaves this nil so the default tgbotapi.NewBotAPI
	// path is used.
	BotFactory alerts.BotFactory

	// AlertDB is an optional pre-opened SQLite database. When non-nil,
	// initPersistence uses this DB directly instead of calling
	// alerts.OpenDB(AlertDBPath). This is the inversion seam that lets
	// app/wire.go open the DB once and construct DB-backed stores
	// (audit, riskguard, billing, invitation) BEFORE kc.NewWithOptions —
	// breaking the cycle where those stores were post-wired via SetX
	// setters from the manager's own AlertDB() accessor. AlertDBPath is
	// ignored when this is non-nil (the manager does not close a DB it
	// did not open).
	AlertDB *alerts.DB

	// AuditStore, RiskGuard, BillingStore, InvitationStore are optional
	// pre-constructed stores. When non-nil, the manager populates the
	// matching field directly during init, replacing the post-construction
	// SetX setter pattern (which is retained as deprecated shims for
	// backward compatibility with the ~70+ test sites that still use it).
	//
	// Production wiring (app/wire.go) uses these in combination with
	// AlertDB to break the construction cycle. Tests that don't need
	// these stores can leave them nil and the manager runs without them
	// (matching legacy behaviour).
	AuditStore      *audit.Store
	RiskGuard       *riskguard.Guard
	BillingStore    *billing.Store
	InvitationStore *users.InvitationStore
}

Config holds configuration for creating a new kc Manager.

Anchor 6 PR 6.15 relocated the canonical declaration from kc/manager.go to its own file so manager.go can stay focused on the constructors only. No behaviour change — pure file move.

type CredentialService

type CredentialService struct {
	// contains filtered or unexported fields
}

CredentialService owns credential resolution: per-user vs global credentials, API key lookup, and registry backfill. Extracted from Manager as part of Clean Architecture / SOLID refactoring.

Dependencies are interface types (Dependency Inversion Principle), enabling mock injection for testing.

func NewCredentialService

func NewCredentialService(cfg CredentialServiceConfig) *CredentialService

NewCredentialService creates a new CredentialService with the given dependencies.

func (*CredentialService) BackfillRegistryFromCredentials

func (cs *CredentialService) BackfillRegistryFromCredentials()

BackfillRegistryFromCredentials syncs existing credentials into the registry. This handles pre-registry self-provisioned keys that were stored before the registry existed.

func (*CredentialService) GetAPIKeyForEmail

func (cs *CredentialService) GetAPIKeyForEmail(email string) string

GetAPIKeyForEmail returns the API key: per-user if registered, otherwise global. Thin delegate to domain.CredentialResolution.APIKey.

func (*CredentialService) GetAPISecretForEmail

func (cs *CredentialService) GetAPISecretForEmail(email string) string

GetAPISecretForEmail returns the API secret: per-user if registered, otherwise global. Thin delegate to domain.CredentialResolution.APISecret.

func (*CredentialService) GetAccessTokenForEmail

func (cs *CredentialService) GetAccessTokenForEmail(email string) string

GetAccessTokenForEmail returns the cached access token for a given email.

func (*CredentialService) HasCachedToken

func (cs *CredentialService) HasCachedToken(email string) bool

HasCachedToken returns true if there's a cached Kite token for the given email.

func (*CredentialService) HasCredentials

func (cs *CredentialService) HasCredentials(email string) bool

HasCredentials returns true if credentials can be resolved for the email (either per-user or global). Delegates to domain.CredentialResolution.IsResolved.

func (*CredentialService) HasGlobalCredentials

func (cs *CredentialService) HasGlobalCredentials() bool

HasGlobalCredentials returns true if global API key/secret are configured (from env vars).

func (*CredentialService) HasPreAuth

func (cs *CredentialService) HasPreAuth() bool

HasPreAuth returns true if the service has a pre-set access token.

func (*CredentialService) HasUserCredentials

func (cs *CredentialService) HasUserCredentials(email string) bool

HasUserCredentials returns true if per-user Kite credentials exist for the given email.

func (*CredentialService) IsTokenValid

func (cs *CredentialService) IsTokenValid(email string) bool

IsTokenValid returns true if the user has a cached Kite token that has not expired. Delegates to domain.Session.IsExpired via the ToDomainSession converter so the 06:00 IST refresh rule lives on the rich entity rather than being re-derived here.

func (*CredentialService) QualifiesForTrading

func (cs *CredentialService) QualifiesForTrading(email string) bool

QualifiesForTrading is the canonical "can this user place orders right now" rule. Combines credential availability + session authenticity via the domain aggregate; no service-layer logic. Single source of truth so every call site (place_order, modify_order, GTT, etc.) gets identical answers.

func (*CredentialService) ResolveCredentials

func (cs *CredentialService) ResolveCredentials(email string) (apiKey, apiSecret string, err error)

ResolveCredentials returns the (apiKey, apiSecret) for a user. Per-user credentials take priority; global credentials are the fallback.

Thin orchestrator: delegates the rule to domain.ResolveCredentials so the per-user-vs-global decision lives on a value object that any layer can consult independently.

type CredentialServiceConfig

type CredentialServiceConfig struct {
	APIKey          string
	APISecret       string
	AccessToken     string
	CredentialStore CredentialStoreInterface
	TokenStore      TokenStoreInterface
	RegistryStore   RegistryStoreInterface
	Logger          *slog.Logger
}

CredentialServiceConfig holds dependencies for creating a CredentialService.

type CredentialStoreInterface

type CredentialStoreInterface interface {
	// Get retrieves stored credentials for the given email.
	Get(email string) (*KiteCredentialEntry, bool)

	// Set stores credentials for the given email.
	Set(email string, entry *KiteCredentialEntry)

	// Delete removes credentials for the given email.
	Delete(email string)

	// ListAll returns a redacted summary of all stored credentials.
	ListAll() []KiteCredentialSummary

	// ListAllRaw returns all credentials unredacted (for internal operations).
	ListAllRaw() []RawCredentialEntry

	// GetSecretByAPIKey finds the API secret for a given API key.
	GetSecretByAPIKey(apiKey string) (apiSecret string, ok bool)

	// Count returns the number of stored credential entries.
	Count() int
}

CredentialStoreInterface defines operations for storing per-user Kite developer app credentials (API key + secret).

type CredentialStoreProvider

type CredentialStoreProvider interface {
	CredentialStore() CredentialStoreInterface
}

CredentialStoreProvider exposes the per-email Kite credential store.

type EventDispatcherProvider

type EventDispatcherProvider interface {
	EventDispatcher() *domain.EventDispatcher
}

EventDispatcherProvider exposes the domain event dispatcher. Returns nil when event-sourcing is disabled (no AlertDB / no event store wiring). Authored to unblock Phase 3a Batch 5 — ext_apps.go + admin_risk_tools.go reach manager.EventDispatcher() directly; this provider lets them go through ToolHandlerDeps.Events instead.

type EventingService

type EventingService struct {
	// contains filtered or unexported fields
}

EventingService groups the domain event dispatcher and append-only event store. Both are optional infrastructure: Manager holds the concrete values and this service mediates access so use-case code can depend on a narrow surface rather than the whole Manager.

Tier 1.2 (Path A.28 follow-up to Tier 1.1's broker_services elimination): the back-pointer to *Manager has been replaced with closures over the underlying fields. Each closure dereferences the source-of-truth Manager field at call time (not at construction time), preserving observable behaviour identical to the prior `e.m.X` access. Critical for SetDispatcher: the propagated subscribers (projector, sessionSvc, trailingStopMgr, 8 Wave-D use-case fields) are nil at newEventingService() time and only populated later by initProjector / initFocusedServices / initTrailingStop / initOrderUseCases. Closures capture by-reference so they read the latest value when SetDispatcher runs.

func (*EventingService) Dispatcher

func (e *EventingService) Dispatcher() *domain.EventDispatcher

Dispatcher returns the domain event dispatcher, or nil if not configured.

func (*EventingService) SetDispatcher

func (e *EventingService) SetDispatcher(d *domain.EventDispatcher)

SetDispatcher sets the domain event dispatcher and subscribes the read-side projector so order/alert/position events flow into the live aggregate maps. Also wires the dispatcher into the session service so new MCP sessions emit SessionCreatedEvent, and into the trailing-stop manager so successful triggers emit TrailingStopTriggeredEvent.

Wave D Slice D2/D3: also propagates the new dispatcher into the startup-once order/GTT use cases the Manager holds. Without this propagation, the use cases would have captured a nil dispatcher at initOrderUseCases time (because production wires the dispatcher AFTER kc.NewWithOptions returns) and silently drop OrderPlaced / OrderModified / OrderCancelled / GTTPlaced / GTTModified / GTTDeleted events — breaking the audit-log persister + read-side projector.

All Set* propagation calls are nil-safe: the use case's setter accepts nil to disable event dispatch, and the Manager-side fields are nil until initOrderUseCases runs (so the early-init path before registerCQRSHandlers is also safe).

func (*EventingService) SetStore

func (e *EventingService) SetStore(s *eventsourcing.EventStore)

SetStore sets the domain audit log.

func (*EventingService) Store

func (e *EventingService) Store() *eventsourcing.EventStore

Store returns the domain audit log (append-only event store), or nil.

type FamilyService

type FamilyService struct {
	// contains filtered or unexported fields
}

FamilyService handles family billing operations — invite, remove, list, tier resolution. Extracts family logic from Manager to reduce god-object coupling.

func NewFamilyService

func NewFamilyService(us FamilyUserStore, bs BillingStoreInterface, is *users.InvitationStore) *FamilyService

NewFamilyService creates a family service with the required stores.

func (*FamilyService) AdminEmailFn

func (f *FamilyService) AdminEmailFn() func(string) string

AdminEmailFn returns a function that resolves a user's admin email for tier inheritance.

func (*FamilyService) CanInvite

func (f *FamilyService) CanInvite(adminEmail string) (bool, int, int)

CanInvite checks if the admin has room for one more family member. Delegates the rule to domain.Family — the service is now a thin orchestrator that fetches counts and lifts them into the aggregate.

Returns (canInvite, currentCount, maxCount). On any error constructing the aggregate (shouldn't happen for valid emails / non-negative counts) returns (false, current, max) so the caller sees a "no" answer rather than a misleading "yes".

func (*FamilyService) ListMembers

func (f *FamilyService) ListMembers(adminEmail string) []*users.User

ListMembers returns all users linked to this admin.

func (*FamilyService) MaxUsers

func (f *FamilyService) MaxUsers(adminEmail string) int

MaxUsers returns the max family members for this admin's plan.

func (*FamilyService) MemberCount

func (f *FamilyService) MemberCount(adminEmail string) int

MemberCount returns how many family members are linked to this admin.

func (*FamilyService) RemoveMember

func (f *FamilyService) RemoveMember(adminEmail, memberEmail string) error

RemoveMember unlinks a family member from the admin.

type FamilyUserStore

type FamilyUserStore interface {
	Get(email string) (*users.User, bool)
	ListByAdminEmail(adminEmail string) []*users.User
	SetAdminEmail(email, adminEmail string) error
}

FamilyUserStore is the narrow interface FamilyService needs from user store. ISP: only Get + ListByAdminEmail + SetAdminEmail.

type FillWatcher

type FillWatcher struct {
	// contains filtered or unexported fields
}

FillWatcher polls the broker for order completion and dispatches OrderFilledEvent. Safe for concurrent Watch calls — each invocation spawns its own goroutine, and the dispatcher is itself concurrency- safe. See fill_watcher_test.go for the contract.

func NewFillWatcher

func NewFillWatcher(cfg FillWatcherConfig) *FillWatcher

NewFillWatcher constructs a FillWatcher from config. Callers must separately invoke Start() to wire the OrderPlacedEvent subscription, or drive Watch() directly for tight control in tests.

If Resolver is nil but Broker is non-nil, the broker is wrapped in a static resolver (useful for tests). Either form requires Dispatcher and Logger.

func (*FillWatcher) Start

func (w *FillWatcher) Start()

Start subscribes the watcher to domain.OrderPlacedEvent so every placed order automatically spawns a poll goroutine. Idempotent only across distinct Dispatchers — subscribing twice to the same dispatcher will produce two goroutines per event.

func (*FillWatcher) Stop

func (w *FillWatcher) Stop()

Stop signals every in-flight pollLoop goroutine to exit promptly. Idempotent — safe to call from both lifecycle.Shutdown and direct test cleanup. After Stop returns, callers should Wait() to confirm goroutines have actually exited (channel close is async).

Stop does NOT unsubscribe from the dispatcher; callers wanting full teardown should drop the dispatcher reference (or use a fresh dispatcher per server instance).

func (*FillWatcher) Wait

func (w *FillWatcher) Wait(timeout time.Duration)

Wait blocks until every in-flight poll goroutine has exited, or the timeout fires (whichever comes first). Primary use is test synchronisation — production code should not call this.

func (*FillWatcher) Watch

func (w *FillWatcher) Watch(placed domain.OrderPlacedEvent)

Watch spawns a goroutine that polls the broker for the given order. Returns immediately — callers that need to synchronise should use Wait().

The ticker is created on the caller goroutine (before we spawn the poll loop) so tests using testutil.FakeClock have a happens-before guarantee that the first Advance() will find a registered ticker. If the ticker were created inside the goroutine, a race between test-thread Advance and goroutine NewTicker would silently drop the first tick.

type FillWatcherBroker

type FillWatcherBroker interface {
	GetOrderHistory(orderID string) ([]broker.Order, error)
}

FillWatcherBroker is the narrow broker surface the watcher depends on. Satisfied by broker.Client (which embeds broker.OrderManager). Tests use a hand-rolled fake instead of the full broker/mock — the GetOrderHistory-only dependency is intentional.

type FillWatcherBrokerResolver

type FillWatcherBrokerResolver interface {
	GetBrokerForEmail(email string) (FillWatcherBroker, error)
}

FillWatcherBrokerResolver resolves a FillWatcherBroker by email. In production this is satisfied by SessionService.GetBrokerForEmail — see app/wire.go for the adapter. Tests can return a fake broker directly without resolving through the session service.

func FillWatcherResolverFromBroker

func FillWatcherResolverFromBroker(r BrokerResolverProvider) FillWatcherBrokerResolver

FillWatcherResolverFromBroker returns a FillWatcherBrokerResolver backed by anything that satisfies BrokerResolverProvider. The caller (app/wire.go) passes *kc.Manager directly post-PR-6.4; pre-PR callers passed *kc.SessionService via the now-deleted FillWatcherResolverFromSessionSvc constructor.

type FillWatcherConfig

type FillWatcherConfig struct {
	Broker       FillWatcherBroker
	Resolver     FillWatcherBrokerResolver
	Dispatcher   *domain.EventDispatcher
	Logger       *slog.Logger
	Clock        clockport.Clock // nil => clockport.RealClock{}
	PollInterval time.Duration   // default: 5 * time.Second
	MaxDuration  time.Duration   // default: 60 * time.Second
}

FillWatcherConfig is the constructor payload. All fields are required except PollInterval/MaxDuration, which default to 5s and 60s.

Exactly one of Broker or Resolver must be set. Broker wraps a single static broker in a resolver for tests; production callers pass a real per-email Resolver.

type IdentityService added in v0.1.5

type IdentityService struct {
	Credential     *CredentialService
	Session        *SessionService
	ManagedSession *ManagedSessionService
	Signer         *SessionSigner
	Lifecycle      *SessionLifecycleService
}

IdentityService bundles the identity / authentication / session-lifecycle services that previously lived as 5 separate fields directly on Manager.

Tier B Step 4 (2026-05-16) — identity bundle. Manager 43 → 38 fields.

Membership decision (per .research/tier-b-steps-4-5-preflight-2026-05-16.md §2.2):

  • Credential — per-user OAuth credential resolution
  • Session — MCP session lifecycle (login → token → session)
  • ManagedSession — thin facade over SessionManager (active count, terminate)
  • Signer — HMAC session signing (crypto bundle, cohesive w/ identity)
  • Lifecycle — thin Manager-shaped facade exposing get/create/clear/ complete via delegation to Session

NOT in scope (stay outside IdentityService):

  • SessionManager (*SessionRegistry) — has 4 external direct-readers (kite-mcp-bootstrap / kite-mcp-usecases); kept as a Manager field so those call sites are not forced to migrate yet (Option A per preflight §2.4). Identity.Session and Identity.ManagedSession both hold a pointer to the same registry, set during initFocusedServices.
  • tokenStore / credentialStore / registryStore / userStore — owned by StoreRegistry per Phase 3a design. Identity reaches them via interface ports threaded through Session/Credential configs.
  • encryptionKey — cross-cutting concern (also used by users.Store TOTP); stays on Manager per Step 3 footnote.

Pattern: matches the Step 2 OrderService + Step 3 AlertService precedent — an empty service is allocated up front in newEmptyManager, then the init* phases populate the fields in-place via same-package field access. After initFocusedServices the bundle is fully populated; consumers reach the individual services via m.Identity.<Field> (e.g. m.Identity.Session).

type InstrumentManagerInterface

type InstrumentManagerInterface = instruments.InstrumentManagerInterface

InstrumentManagerInterface is the instrument-metadata lookup interface. Anchor 5 PR 5.4 relocated the canonical declaration to kc/instruments/manager_interface.go (its owning package); this alias preserves the legacy `kc.InstrumentManagerInterface` reference path so the 10+ pre-existing reverse-dep call sites build unchanged. Type aliases are not new types — `kc.InstrumentManagerInterface` and `instruments.InstrumentManagerInterface` are interchangeable at every call site, including the compile-time satisfaction check below (`_ InstrumentManagerInterface = (*instruments.Manager)(nil)`).

Wave B-2 PR 5.5 will rewrite kc/ports/instrument.go to reference `instruments.InstrumentManagerInterface` directly so it can drop its kc-parent import; this alias remains as the long-tail backward- compatibility shim until call sites migrate.

type KiteClientFactory

type KiteClientFactory = zerodha.KiteClientFactory

KiteClientFactory creates Kite API clients. Inject a mock in tests by returning any zerodha.KiteSDK implementation (e.g. zerodha.MockKiteSDK) that replays canned responses without touching HTTP.

This factory is used by background services (briefing, pnl snapshots, telegram bot) that run outside MCP tool handlers and therefore don't have access to a session-pinned broker. The return type is the broker-owned zerodha.KiteSDK interface, NOT the raw SDK *kiteconnect.Client — the concrete kiteconnect.New call site is confined to broker/zerodha (the single-seam guarantee that the hexagonal-100 claim always promised).

F4 consolidation (Phase B/D close-out): canonical declaration moved to broker/zerodha (commit-this). kc.KiteClientFactory is now a Go type alias preserving the historical name + import path while pointing at the single source of truth. kc/telegram.KiteClientFactory got the same alias treatment. kc/alerts.KiteClientFactory stays as a narrow 1-method subset (intentional ISP narrowness — briefing only needs NewClientWithToken).

type KiteConnect

type KiteConnect struct {
	// Client is the authenticated Kite SDK. Exported because 23+ tool handlers access it directly.
	Client zerodha.KiteSDK
}

KiteConnect wraps the Kite Connect client.

The Client field holds a zerodha.KiteSDK (interface) rather than a concrete *kiteconnect.Client so background services and tool handlers both consume the same hexagonal port — the broker-owned interface collapses the former two SDK construction sites (kc/kite_client.go and broker/zerodha/sdk_adapter.go) into one, and lets tests swap in zerodha.MockKiteSDK without touching HTTP.

Anchor 6 PR 6.15 relocated this from kc/manager.go to keep manager.go focused on the constructor surface only.

func NewKiteConnect

func NewKiteConnect(apiKey string, factory ...KiteClientFactory) *KiteConnect

NewKiteConnect creates a new KiteConnect instance. All SDK instantiation routes through the KiteClientFactory interface.

type KiteCredentialEntry

type KiteCredentialEntry struct {
	APIKey    string
	APISecret string
	AppID     string // AppID = API key for Kite developer apps
	StoredAt  time.Time
}

KiteCredentialEntry stores a user's Kite developer app credentials.

type KiteCredentialStore

type KiteCredentialStore struct {
	// contains filtered or unexported fields
}

KiteCredentialStore is a thread-safe in-memory map of email -> Kite developer credentials. Optionally backed by SQLite for persistence via SetDB.

func NewKiteCredentialStore

func NewKiteCredentialStore() *KiteCredentialStore

NewKiteCredentialStore creates a new credential store.

func (*KiteCredentialStore) Count

func (s *KiteCredentialStore) Count() int

Count returns the number of stored credential entries.

func (*KiteCredentialStore) Delete

func (s *KiteCredentialStore) Delete(email string)

Delete removes credentials for the given email.

func (*KiteCredentialStore) Get

Get retrieves stored credentials for the given email. Returns a copy to prevent callers from mutating shared state.

func (*KiteCredentialStore) GetSecretByAPIKey

func (s *KiteCredentialStore) GetSecretByAPIKey(apiKey string) (apiSecret string, ok bool)

GetSecretByAPIKey finds the API secret for a given API key by scanning all stored credentials. Used when the client_id (= API key) is known but the email is not yet resolved.

func (*KiteCredentialStore) ListAll

ListAll returns a redacted summary of all stored credentials.

func (*KiteCredentialStore) ListAllRaw

func (s *KiteCredentialStore) ListAllRaw() []RawCredentialEntry

ListAllRaw returns all credentials unredacted. Used internally for registry backfill.

func (*KiteCredentialStore) LoadFromDB

func (s *KiteCredentialStore) LoadFromDB() error

LoadFromDB populates the in-memory store from the database.

func (*KiteCredentialStore) OnTokenInvalidate

func (s *KiteCredentialStore) OnTokenInvalidate(fn func(email string))

OnTokenInvalidate registers a callback that is invoked when a user's API key changes, so the stale cached Kite token (issued for the old app) can be deleted.

func (*KiteCredentialStore) Set

func (s *KiteCredentialStore) Set(email string, entry *KiteCredentialEntry)

Set stores credentials for the given email.

func (*KiteCredentialStore) SetDB

func (s *KiteCredentialStore) SetDB(db *alerts.DB)

SetDB enables write-through persistence to the given SQLite database.

func (*KiteCredentialStore) SetLogger

func (s *KiteCredentialStore) SetLogger(logger *slog.Logger)

SetLogger sets the logger for DB error reporting.

type KiteCredentialSummary

type KiteCredentialSummary struct {
	Email         string    `json:"email"`
	APIKey        string    `json:"api_key"`
	APISecretHint string    `json:"api_secret_hint"`
	StoredAt      time.Time `json:"stored_at"`
}

KiteCredentialSummary is a redacted view of a credential entry (API secret masked).

type KiteSessionData

type KiteSessionData = domain.KiteSessionData

KiteSessionData is the transient runtime state of an authenticated MCP session. Anchor 5 PR 5.6 relocated the canonical declaration to kc/domain/session.go (its proper bounded-context home — see anchor-5-prs-design.md). This alias preserves the legacy kc.KiteSessionData reference path so the 56-file reverse-dep set continues to compile via existing imports.

The PR also collapsed the kc.KiteConnect wrapper indirection: the Kite field is now zerodha.KiteSDK (interface) directly, not *KiteConnect (one-field struct holding the same SDK). All callsites migrated from `.Kite.Client.METHOD(...)` to `.Kite.METHOD(...)`. The kc.KiteConnect type + NewKiteConnect helper remain available (see kc/kite_connect.go) for the few callers that still need a *KiteConnect pointer; the field type change is a deliberate decoupling so kc/domain can host KiteSessionData without depending on the kc parent package.

type KiteTokenEntry

type KiteTokenEntry struct {
	AccessToken string
	UserID      string
	UserName    string
	StoredAt    time.Time
}

KiteTokenEntry stores a Kite access token and metadata for a user.

type KiteTokenStore

type KiteTokenStore struct {
	// contains filtered or unexported fields
}

KiteTokenStore is a thread-safe in-memory map of email -> Kite access token. Used to cache tokens so users only need to login once per day. Optionally backed by SQLite for persistence via SetDB.

func NewKiteTokenStore

func NewKiteTokenStore() *KiteTokenStore

NewKiteTokenStore creates a new token store.

func (*KiteTokenStore) Count

func (s *KiteTokenStore) Count() int

Count returns the number of stored tokens.

func (*KiteTokenStore) Delete

func (s *KiteTokenStore) Delete(email string)

Delete removes a token for the given email.

func (*KiteTokenStore) Get

func (s *KiteTokenStore) Get(email string) (*KiteTokenEntry, bool)

Get retrieves a stored token for the given email. Returns a copy to prevent callers from mutating shared state.

func (*KiteTokenStore) ListAll

func (s *KiteTokenStore) ListAll() []KiteTokenSummary

ListAll returns a redacted summary of all cached tokens.

func (*KiteTokenStore) LoadFromDB

func (s *KiteTokenStore) LoadFromDB() error

LoadFromDB populates the in-memory store from the database.

func (*KiteTokenStore) OnChange

func (s *KiteTokenStore) OnChange(cb TokenChangeCallback)

OnChange registers a callback that fires when a token is stored or updated.

func (*KiteTokenStore) Set

func (s *KiteTokenStore) Set(email string, entry *KiteTokenEntry)

Set stores a token for the given email and notifies observers. The entry is copied before storing to prevent the caller from mutating shared state.

func (*KiteTokenStore) SetDB

func (s *KiteTokenStore) SetDB(db *alerts.DB)

SetDB enables write-through persistence to the given SQLite database.

func (*KiteTokenStore) SetLogger

func (s *KiteTokenStore) SetLogger(logger *slog.Logger)

SetLogger sets the logger for DB error reporting.

type KiteTokenSummary

type KiteTokenSummary struct {
	Email    string    `json:"email"`
	UserID   string    `json:"user_id"`
	UserName string    `json:"user_name"`
	StoredAt time.Time `json:"stored_at"`
}

KiteTokenSummary is a redacted view of a token entry (no AccessToken exposed).

type MCPServerProvider

type MCPServerProvider interface {
	MCPServer() any
}

MCPServerProvider exposes the stored MCP server reference. Returns nil before the server has been attached.

type MCPSession

type MCPSession struct {
	ID         string
	Terminated bool
	CreatedAt  time.Time
	ExpiresAt  time.Time
	Data       any // Contains KiteSessionData

	// ClientHint is a free-form description of the MCP surface that created
	// the session (e.g. the raw User-Agent from the bearer-issuance HTTP
	// request, or a constant like "claude-code" / "claude-desktop"). Empty
	// when the registry was called through a code path that has no HTTP
	// request context (SessionIdManager.Generate(), SSE/stdio transports).
	// Rendered as "Unknown" by list_mcp_sessions when empty.
	ClientHint string
}

type ManagedSessionService

type ManagedSessionService struct {
	// contains filtered or unexported fields
}

ManagedSessionService wraps session operations for cleaner access. Extracted from Manager to reduce god-object coupling.

func NewManagedSessionService

func NewManagedSessionService(reg *SessionRegistry) *ManagedSessionService

NewManagedSessionService creates a new ManagedSessionService.

func (*ManagedSessionService) ActiveCount

func (s *ManagedSessionService) ActiveCount() int

ActiveCount returns the number of non-terminated, non-expired sessions.

func (*ManagedSessionService) Registry

func (s *ManagedSessionService) Registry() *SessionRegistry

Registry returns the underlying SessionRegistry.

func (*ManagedSessionService) TerminateByEmail

func (s *ManagedSessionService) TerminateByEmail(email string) int

TerminateByEmail terminates all active sessions belonging to the given email.

type Manager

type Manager struct {
	Logger *slog.Logger

	// Focused service objects (Clean Architecture)
	Identity      *IdentityService  // identity bundle: Credential / Session / ManagedSession / Signer / Lifecycle (Tier B Step 4)
	PortfolioSvc  *PortfolioService // portfolio queries (holdings, positions, margins, profile)
	OrderSvc      *OrderService     // order placement, modification, cancellation
	AlertSvc      *AlertService     // alert lifecycle (CRUD, evaluation, trailing stops, Telegram, P&L)
	FamilyService *FamilyService    // family billing (invite, remove, list, tier resolution)

	Instruments    *instruments.Manager
	SessionManager *SessionRegistry
	// contains filtered or unexported fields
}

func New deprecated

func New(cfg Config) (*Manager, error)

New creates a new kc Manager with the given configuration.

Deprecated: prefer NewWithOptions(ctx, opts...) which uses the functional-options pattern consistent with the rest of the codebase (testutil/kcfixture, kc/ticker/config.go, kc/scheduler/provider.go). This function is retained as a thin backward-compat shim because 40+ test files across app/, mcp/, and kc/ops/ call it directly with literal kc.Config{…} structs; forcing all of them to migrate at once would ripple through three scopes owned by other active agents.

New is equivalent to NewWithOptions(context.Background(), WithConfig(cfg)). It validates cfg.Logger is non-nil (matching pre-shim behaviour) before delegating — preserving the error class "logger is required" so existing tests that rely on that exact error message keep passing.

func NewManager deprecated

func NewManager(apiKey, apiSecret string, logger *slog.Logger) (*Manager, error)

NewManager creates a new manager with default configuration.

Deprecated: Use New(Config{APIKey: apiKey, APISecret: apiSecret, Logger: logger}) instead. NOTE: Still used by kc/manager_test.go (TestNewManager). Remove once tests are migrated to New().

func NewWithOptions

func NewWithOptions(ctx context.Context, opts ...Option) (*Manager, error)

NewWithOptions creates a new kc Manager from a base context plus a list of functional options. Primary constructor for the Manager — backward-compat paths flow through New(Config) above.

The body is a thin orchestrator over the init* helpers in kc/manager_init.go. Each helper is documented at its declaration site; the order below is load-bearing — downstream phases read state that earlier phases wrote. Do not reorder without re-reading the helper docs.

ctx is currently stashed on the options payload for future use (cancellable init, tracing spans, deadline propagation); no init phase consumes it yet, but the plumbing is in place so that flip does not later become a breaking change.

func (*Manager) APIKey

func (m *Manager) APIKey() string

APIKey returns the global Kite API key.

func (*Manager) AdminSecretPath

func (m *Manager) AdminSecretPath() string

AdminSecretPath returns the configured admin secret path.

func (*Manager) AlertDB

func (m *Manager) AlertDB() *alerts.DB

AlertDB returns the optional SQLite database used for persistence.

func (*Manager) AlertStore

func (m *Manager) AlertStore() AlertStoreInterface

AlertStore returns the per-user alert store.

func (*Manager) AlertStoreConcrete

func (m *Manager) AlertStoreConcrete() *alerts.Store

AlertStoreConcrete returns the concrete alert store.

func (*Manager) AuditStore

func (m *Manager) AuditStore() AuditStoreInterface

AuditStore returns the audit trail store, or nil if not configured.

func (*Manager) AuditStoreConcrete

func (m *Manager) AuditStoreConcrete() *audit.Store

AuditStoreConcrete returns the concrete audit store.

func (*Manager) BillingStore

func (m *Manager) BillingStore() BillingStoreInterface

BillingStore returns the billing store, or nil if not configured.

func (*Manager) BillingStoreConcrete

func (m *Manager) BillingStoreConcrete() *billing.Store

BillingStoreConcrete returns the concrete billing store.

func (*Manager) Brokers

func (m *Manager) Brokers() *BrokerServices

Brokers returns the broker services group.

func (*Manager) CleanupExpiredSessions

func (m *Manager) CleanupExpiredSessions() int

CleanupExpiredSessions manually triggers cleanup of expired MCP sessions.

func (*Manager) ClearSession

func (m *Manager) ClearSession(sessionID string)

ClearSession terminates a session, triggering cleanup hooks.

func (*Manager) ClearSessionData

func (m *Manager) ClearSessionData(sessionID string) error

ClearSessionData clears the session data without terminating the session.

func (*Manager) CommandBus

func (m *Manager) CommandBus() *cqrs.InMemoryBus

CommandBus returns the CQRS command bus for write-side dispatches.

func (*Manager) CompleteSession

func (m *Manager) CompleteSession(mcpSessionID, kiteRequestToken string) error

CompleteSession completes Kite authentication using the request token. Back-compat shim — new callers should use CompleteSessionAndRotate to receive the post-auth rotated session ID (OWASP A07 defence).

func (*Manager) CompleteSessionAndRotate

func (m *Manager) CompleteSessionAndRotate(mcpSessionID, kiteRequestToken string) (string, error)

CompleteSessionAndRotate completes Kite authentication and rotates the session ID. See SessionService.CompleteSessionAndRotate for semantics.

func (*Manager) CredentialStore

func (m *Manager) CredentialStore() CredentialStoreInterface

CredentialStore returns the per-email Kite credential store.

func (*Manager) CredentialStoreConcrete

func (m *Manager) CredentialStoreConcrete() *KiteCredentialStore

CredentialStoreConcrete returns the concrete credential store.

func (*Manager) DevMode

func (m *Manager) DevMode() bool

DevMode returns true if the server is running in development mode with mock broker.

func (*Manager) EventDispatcher

func (m *Manager) EventDispatcher() *domain.EventDispatcher

EventDispatcher returns the domain event dispatcher, or nil if not configured.

func (*Manager) EventStoreConcrete

func (m *Manager) EventStoreConcrete() *eventsourcing.EventStore

EventStoreConcrete returns the domain audit log, or nil if not configured.

func (*Manager) Eventing

func (m *Manager) Eventing() *EventingService

Eventing returns the eventing service.

func (*Manager) ExternalURL

func (m *Manager) ExternalURL() string

ExternalURL returns the configured external URL (e.g. "https://kite-mcp-server.fly.dev").

func (*Manager) ForceInstrumentsUpdate

func (m *Manager) ForceInstrumentsUpdate() error

ForceInstrumentsUpdate forces an immediate instruments update.

func (*Manager) GenerateSession

func (m *Manager) GenerateSession() string

GenerateSession creates a new MCP session and returns its ID.

func (*Manager) GetAPIKeyForEmail

func (m *Manager) GetAPIKeyForEmail(email string) string

GetAPIKeyForEmail returns the API key for a user (per-user or global fallback).

func (*Manager) GetAPISecretForEmail

func (m *Manager) GetAPISecretForEmail(email string) string

GetAPISecretForEmail returns the API secret for a user (per-user or global fallback).

func (*Manager) GetAccessTokenForEmail

func (m *Manager) GetAccessTokenForEmail(email string) string

GetAccessTokenForEmail returns the cached access token for the given email.

func (*Manager) GetActiveSessionCount

func (m *Manager) GetActiveSessionCount() int

GetActiveSessionCount returns the number of active sessions.

func (*Manager) GetBrokerForEmail

func (m *Manager) GetBrokerForEmail(email string) (broker.Client, error)

GetBrokerForEmail resolves the broker.Client for the given email by delegating to the underlying SessionService. Anchor 6 PR 6.4 (per .research/anchor-6-pr-6-4-broker-resolver-redesign.md commit a2a11db): added so *Manager satisfies the narrowed BrokerResolverProvider interface (kc/manager_interfaces.go:95-114) directly, without exposing the *SessionService wrapper. Replaces the prior `manager.SessionSvc().GetBrokerForEmail(email)` two-hop at all 4 cross-package call sites.

func (*Manager) GetInstrumentsStats

func (m *Manager) GetInstrumentsStats() instruments.UpdateStats

GetInstrumentsStats returns current instruments update statistics.

func (*Manager) GetOrCreateSession

func (m *Manager) GetOrCreateSession(mcpSessionID string) (*KiteSessionData, bool, error)

GetOrCreateSession retrieves an existing Kite session or creates a new one.

func (*Manager) GetOrCreateSessionWithEmail

func (m *Manager) GetOrCreateSessionWithEmail(mcpSessionID, email string) (*KiteSessionData, bool, error)

GetOrCreateSessionWithEmail retrieves or creates a Kite session with email context.

func (*Manager) GetSession

func (m *Manager) GetSession(mcpSessionID string) (*KiteSessionData, error)

GetSession retrieves an existing Kite session by MCP session ID.

func (*Manager) HandleKiteCallback

func (m *Manager) HandleKiteCallback() func(w http.ResponseWriter, r *http.Request)

HandleKiteCallback returns an HTTP handler for Kite authentication callbacks. Kept as a Manager method for backward compatibility with existing callers in app/http.go and the test suite.

func (*Manager) HasBrokerFactory

func (m *Manager) HasBrokerFactory() bool

HasBrokerFactory reports whether the underlying SessionService has an explicit broker.Factory wired. Anchor 6 PR 6.4: added so *Manager satisfies BrokerResolverProvider directly. Replaces the prior `manager.SessionSvc().HasBrokerFactory()` two-hop at the app/http.go:720 call site.

func (*Manager) HasCachedToken

func (m *Manager) HasCachedToken(email string) bool

HasCachedToken returns true if there's a cached Kite token for the given email.

func (*Manager) HasGlobalCredentials

func (m *Manager) HasGlobalCredentials() bool

HasGlobalCredentials returns true if global API key/secret are configured.

func (*Manager) HasMetrics

func (m *Manager) HasMetrics() bool

HasMetrics returns true if metrics manager is available.

func (*Manager) HasPreAuth

func (m *Manager) HasPreAuth() bool

HasPreAuth returns true if the manager has a pre-set access token.

func (*Manager) HasUserCredentials

func (m *Manager) HasUserCredentials(email string) bool

HasUserCredentials returns true if per-user Kite credentials exist for the given email.

func (*Manager) IncrementDailyMetric

func (m *Manager) IncrementDailyMetric(key string)

IncrementDailyMetric increments a daily metric counter by 1.

func (*Manager) IncrementMetric

func (m *Manager) IncrementMetric(key string)

IncrementMetric increments a metric counter by 1.

func (*Manager) InstrumentsManager

func (m *Manager) InstrumentsManager() InstrumentManagerInterface

InstrumentsManager returns the instruments manager.

func (*Manager) InstrumentsManagerConcrete

func (m *Manager) InstrumentsManagerConcrete() *instruments.Manager

InstrumentsManagerConcrete returns the concrete instruments manager.

func (*Manager) InvitationStore

func (m *Manager) InvitationStore() *users.InvitationStore

InvitationStore returns the invitation store, or nil if not configured.

func (*Manager) IsLocalMode

func (m *Manager) IsLocalMode() bool

IsLocalMode returns true when running in STDIO mode (local process, not remote HTTP).

func (*Manager) IsTokenValid

func (m *Manager) IsTokenValid(email string) bool

IsTokenValid returns true if the user has a cached Kite token that has not expired.

func (*Manager) KiteClientFactory

func (m *Manager) KiteClientFactory() KiteClientFactory

KiteClientFactory returns the factory used to create zerodha.KiteSDK instances.

func (*Manager) MCPServer

func (m *Manager) MCPServer() any

MCPServer returns the stored MCP server reference, or nil.

func (*Manager) OpenBrowser

func (m *Manager) OpenBrowser(rawURL string) error

OpenBrowser opens the given URL in the user's default browser. Only works in local/STDIO mode where the server runs on the user's machine.

func (*Manager) PaperEngine

func (m *Manager) PaperEngine() PaperEngineInterface

PaperEngine returns the paper trading engine, or nil if not configured.

func (*Manager) PaperEngineConcrete

func (m *Manager) PaperEngineConcrete() *papertrading.PaperEngine

PaperEngineConcrete returns the concrete paper engine.

func (*Manager) PnLService

func (m *Manager) PnLService() *alerts.PnLSnapshotService

PnLService returns the P&L snapshot service (nil if not initialized).

func (*Manager) QualifiesForTrading

func (m *Manager) QualifiesForTrading(email string) bool

QualifiesForTrading delegates to CredentialSvc.QualifiesForTrading. Returns true iff the user has resolvable credentials AND a non-expired cached Kite token.

func (*Manager) QueryBus

func (m *Manager) QueryBus() *cqrs.InMemoryBus

QueryBus returns the CQRS query bus for read-side dispatches.

func (*Manager) RegistryStore

func (m *Manager) RegistryStore() RegistryStoreInterface

RegistryStore returns the key registry store.

func (*Manager) RegistryStoreConcrete

func (m *Manager) RegistryStoreConcrete() *registry.Store

RegistryStoreConcrete returns the concrete registry store.

func (*Manager) RiskGuard

func (m *Manager) RiskGuard() *riskguard.Guard

RiskGuard returns the riskguard instance, or nil if not configured.

func (*Manager) Scheduling

func (m *Manager) Scheduling() *SchedulingService

Scheduling returns the scheduling service.

func (*Manager) SessionLifecycle

func (m *Manager) SessionLifecycle() *SessionLifecycleService

SessionLifecycle returns the session lifecycle facade.

func (*Manager) SessionLoginURL

func (m *Manager) SessionLoginURL(mcpSessionID string) (string, error)

SessionLoginURL returns the Kite login URL for the given session.

func (*Manager) SessionRegistry added in v0.1.2

func (m *Manager) SessionRegistry() *SessionRegistry

SessionRegistry returns the concrete session registry. Added so that *Manager satisfies ports.SessionRegistryProvider — the Provider port that Phase 3 sub-git brief 3 (kite-mcp-tools-ops) consumes via ToolHandlerDeps in kite-mcp-tools-common, replacing the two residual `manager.SessionManager` field-access call sites in mcp/misc/session_admin_tools.go (admin operations like ListActiveSessions, TerminateByEmail).

The field `SessionManager *SessionRegistry` (manager_struct.go:91) remains the canonical home — this method is a thin one-line passthrough so that the abstract Provider-port surface is satisfied without breaking direct-field consumers (which the B4 work established as the preferred internal access pattern).

Identifier discipline: the method name `SessionRegistry` is distinct from the field name `SessionManager`, so no Go method/field collision.

func (*Manager) SetAuditStore

func (m *Manager) SetAuditStore(store *audit.Store)

SetAuditStore wires the audit store.

func (*Manager) SetBillingStore

func (m *Manager) SetBillingStore(store *billing.Store)

SetBillingStore sets the billing store.

func (*Manager) SetEventDispatcher

func (m *Manager) SetEventDispatcher(d *domain.EventDispatcher)

SetEventDispatcher sets the domain event dispatcher.

func (*Manager) SetEventStore

func (m *Manager) SetEventStore(s *eventsourcing.EventStore)

SetEventStore sets the domain audit log.

func (*Manager) SetFamilyService

func (m *Manager) SetFamilyService(fs *FamilyService)

SetFamilyService sets the family billing service. Anchor 6 PR 6.12: the FamilyService accessor method was deleted in favour of direct field access (m.FamilyService — capitalised), but the setter is retained because:

  1. It encapsulates the assignment for the Fx-graph wiring path in app/providers/family.go (the family setter-after-construct pattern that wire.go's lifecycle requires).
  2. Multiple test fixtures (app/app_edge_test.go, mcp/admin_tools_test.go, mcp/helpers_test.go) seed the family service via this setter; preserving it keeps the test surface stable.
  3. It does NOT conflict with the field — methods and fields with the same name DO clash, but only when both the getter method and field share the name. Setter `SetFamilyService` is a distinct identifier from the field `FamilyService`, so no conflict.

func (*Manager) SetInvitationStore

func (m *Manager) SetInvitationStore(store *users.InvitationStore)

SetInvitationStore sets the invitation store.

func (*Manager) SetKiteClientFactory

func (m *Manager) SetKiteClientFactory(f KiteClientFactory)

SetKiteClientFactory overrides the default factory. Intended for tests.

func (*Manager) SetMCPServer

func (m *Manager) SetMCPServer(srv any)

SetMCPServer stores a reference to the MCP server for elicitation support.

func (*Manager) SetPaperEngine

func (m *Manager) SetPaperEngine(e *papertrading.PaperEngine)

SetPaperEngine sets the paper trading engine.

func (*Manager) SetPnLService

func (m *Manager) SetPnLService(svc *alerts.PnLSnapshotService)

SetPnLService sets the P&L snapshot service.

func (*Manager) SetRiskGuard

func (m *Manager) SetRiskGuard(guard *riskguard.Guard)

SetRiskGuard sets the riskguard for financial safety controls.

func (*Manager) Shutdown

func (m *Manager) Shutdown()

Shutdown gracefully shuts down the manager and all its components.

func (*Manager) StopCleanupRoutine

func (m *Manager) StopCleanupRoutine()

StopCleanupRoutine stops the background cleanup routine.

func (*Manager) Stores

func (m *Manager) Stores() *StoreRegistry

Stores returns the store registry.

func (*Manager) TelegramNotifier

func (m *Manager) TelegramNotifier() *alerts.TelegramNotifier

TelegramNotifier returns the Telegram alert sender (nil if not configured).

func (*Manager) TelegramStore

func (m *Manager) TelegramStore() TelegramStoreInterface

TelegramStore returns the per-user Telegram chat ID store.

func (*Manager) TickerService

func (m *Manager) TickerService() TickerServiceInterface

TickerService returns the per-user WebSocket ticker service.

func (*Manager) TickerServiceConcrete

func (m *Manager) TickerServiceConcrete() *ticker.Service

TickerServiceConcrete returns the concrete ticker service.

func (*Manager) TokenStore

func (m *Manager) TokenStore() TokenStoreInterface

TokenStore returns the per-email token store.

func (*Manager) TokenStoreConcrete

func (m *Manager) TokenStoreConcrete() *KiteTokenStore

TokenStoreConcrete returns the concrete token store.

func (*Manager) TrackDailyUser

func (m *Manager) TrackDailyUser(userID string)

TrackDailyUser records a unique user interaction for today's counter.

func (*Manager) TrailingStopManager

func (m *Manager) TrailingStopManager() *alerts.TrailingStopManager

TrailingStopManager returns the trailing stop-loss manager.

func (*Manager) UpdateInstrumentsConfig

func (m *Manager) UpdateInstrumentsConfig(config *instruments.UpdateConfig)

UpdateInstrumentsConfig updates the instruments manager configuration.

func (*Manager) UserStore

func (m *Manager) UserStore() UserStoreInterface

UserStore returns the user identity store.

func (*Manager) UserStoreConcrete

func (m *Manager) UserStoreConcrete() *users.Store

UserStoreConcrete returns the concrete user store.

func (*Manager) WatchlistStore

func (m *Manager) WatchlistStore() WatchlistStoreInterface

WatchlistStore returns the per-user watchlist store.

func (*Manager) WatchlistStoreConcrete

func (m *Manager) WatchlistStoreConcrete() *watchlist.Store

WatchlistStoreConcrete returns the concrete watchlist store.

type ManagerLifecycle

type ManagerLifecycle interface {
	// Shutdown gracefully shuts down the manager and all its components.
	Shutdown()

	// OpenBrowser opens a URL in the user's default browser (local mode only).
	OpenBrowser(rawURL string) error

	// CleanupExpiredSessions manually triggers cleanup of expired sessions.
	CleanupExpiredSessions() int

	// StopCleanupRoutine stops the background cleanup routine.
	StopCleanupRoutine()
}

ManagerLifecycle manages the lifecycle of the Manager and its sub-components.

type MetricsRecorder

type MetricsRecorder interface {
	// HasMetrics returns true if metrics manager is available.
	HasMetrics() bool

	// IncrementMetric increments a metric counter by 1.
	IncrementMetric(key string)

	// TrackDailyUser records a unique user interaction.
	TrackDailyUser(userID string)

	// IncrementDailyMetric increments a daily metric counter.
	IncrementDailyMetric(key string)
}

MetricsRecorder records operational metrics.

type OAuthBridgeRegistrarDeps

type OAuthBridgeRegistrarDeps struct {
	UserStore       *users.Store
	TokenStore      *KiteTokenStore
	CredentialStore *KiteCredentialStore
	RegistryStore   *registry.Store
	AlertDBGetter   func() *alerts.DB // lazy access for OAuth client store; nil-safe
}

OAuthBridgeRegistrarDeps holds the store dependencies needed to register the OAuth-bridge command handlers. The package-level registrar consumes this struct so it can be shared across multiple bus instances (production Manager + the local-bus mirror in app/adapters_local_bus.go style follow-ups, if any).

Note: AlertDB is provided as a getter closure (not a *alerts.DB value directly) because the OAuth-client-store handler reaches it lazily — only when a SaveOAuthClient/DeleteOAuthClient command arrives. The closure preserves the original "evaluate at command-dispatch time" semantics; constructing the deps eagerly with a *alerts.DB would dereference Manager.AlertDB() at registration time and panic on test fixtures that don't initialize the stores facade.

type Option

type Option func(*options)

Option mutates the Manager construction payload. Returned from the With* helpers; consumed by NewWithOptions (and, as a backward-compat shim, by the deprecated New(Config)).

Design notes:

  • All options are additive. Later options win when they overlap (e.g. WithConfig followed by WithLogger overrides Config.Logger with the explicit logger). This matches how the rest of the codebase uses functional options (testutil/kcfixture, kc/ticker/config.go, kc/scheduler/provider.go) — predictable last-wins rather than first-wins or accumulator.

  • The receiver is *options (not options) so helpers that need to inspect prior state to refine a field can read what earlier options wrote. Not used today; kept for future flexibility.

  • Error-returning options are not supported. Any validation that COULD fail (credential parsing, logger nil-check, context cancellation) is deferred to NewWithOptions' body so the Option surface stays as pure data. This matches functional-option convention in Go — errors flow through the constructor, not through each setter.

func WithAccessToken

func WithAccessToken(token string) Option

WithAccessToken sets a pre-authenticated Kite access token. Used by local-dev and single-user deployments to skip the browser login; on the hosted multi-user deployment this is always empty and tokens are acquired per-user via the OAuth flow.

func WithAdminSecretPath

func WithAdminSecretPath(path string) Option

WithAdminSecretPath sets the admin endpoint's secret path segment so /admin/<secret>/ops can be generated by the dashboard link helpers. Empty disables the admin link emission.

func WithAlertDB

func WithAlertDB(db *alerts.DB) Option

WithAlertDB threads a pre-opened *alerts.DB through Manager construction. When supplied, initPersistence skips the alerts.OpenDB(AlertDBPath) call and uses the externally-opened DB directly. This is the inversion seam for the AlertDB construction cycle: app/wire.go opens the DB once, constructs DB-backed stores (audit/riskguard/billing/invitation) via the matching With* options, then passes them all to NewWithOptions in one hop — eliminating the post-construction SetX setters that previously required kcManager.AlertDB() to be readable BEFORE the manager finished initialising.

Lifetime: the manager does NOT close a DB it did not open. Callers that pass a DB via WithAlertDB own the DB lifecycle and are responsible for closing it during graceful shutdown. The app/lifecycle.go LifecycleManager handles this for app/wire.go.

func WithAlertDBPath

func WithAlertDBPath(path string) Option

WithAlertDBPath enables SQLite persistence for alerts, tokens, credentials, watchlists, users, registry, sessions, and the domain event store. When empty the server runs in-memory only; persistence is all-or-nothing because every store shares the same DB handle.

func WithAppMode

func WithAppMode(mode string) Option

WithAppMode sets the transport mode — "stdio" (default, local), "http", or "sse". Drives IsLocalMode() which gates OpenBrowser and other local-only behaviours.

func WithAuditStore

func WithAuditStore(s *audit.Store) Option

WithAuditStore threads a pre-constructed audit.Store through Manager construction, eliminating the post-init kcManager.SetAuditStore call site in app/wire.go. The legacy SetAuditStore setter is retained as a deprecated shim so the ~5+ test sites that mutate the manager after construction continue to work unchanged.

func WithBillingStore

func WithBillingStore(s *billing.Store) Option

WithBillingStore threads a pre-constructed billing.Store through Manager construction. Same deprecation contract as WithAuditStore.

func WithBotFactory

func WithBotFactory(factory alerts.BotFactory) Option

WithBotFactory installs a per-Manager Telegram bot factory. When supplied, the manager constructs its TelegramNotifier via the factory instead of consulting the kc/alerts package-level newBotFunc global — this is the t.Parallel-safe entry point for tests that need a fake Telegram server. Production wiring omits this so the default tgbotapi.NewBotAPI path is used.

func WithConfig

func WithConfig(cfg Config) Option

WithConfig installs an entire Config struct in one hop. The primary backward-compat path: the deprecated New(cfg) shim delegates to NewWithOptions(ctx, WithConfig(cfg)), so every existing caller (app/wire.go, testutil/kcfixture, 40+ test files under app/, mcp/, kc/ops/) keeps compiling unchanged. New code should prefer the granular setters below unless it already holds a full Config from another source (e.g. env-parsing).

func WithContext

func WithContext(ctx context.Context) Option

WithContext attaches the caller's context to the Manager build. Today the context is not threaded into the init phases (each phase is synchronous and fast), but it is captured so future reshaping — cancellable init, tracing spans, deadline propagation — has the plumbing already in place. Defaults to context.Background() when NewWithOptions is called without this option.

func WithDevMode

func WithDevMode(enabled bool) Option

WithDevMode toggles the mock-broker path. When true, the Manager constructs a broker/mock client and skips real Kite login; used by the devmode test suite and local dev without Kite credentials.

func WithEncryptionSecret

func WithEncryptionSecret(secret string) Option

WithEncryptionSecret supplies the secret used to derive the credential-encryption key (via HKDF in alerts.EnsureEncryptionSalt). Typically the same value as OAUTH_JWT_SECRET so operators manage one secret, not two. Empty disables credential encryption at rest — safe for dev, fatal for production (caller enforces).

func WithExternalURL

func WithExternalURL(url string) Option

WithExternalURL sets the server's externally-reachable URL (e.g. "https://kite-mcp-server.fly.dev"). Used to construct dashboard links and OAuth redirect URIs.

func WithInstrumentsConfig

func WithInstrumentsConfig(cfg *instruments.UpdateConfig) Option

WithInstrumentsConfig overrides the default instruments update configuration (refresh interval, timeout, TestData seed). Ignored when WithInstrumentsManager is also applied.

func WithInstrumentsManager

func WithInstrumentsManager(m *instruments.Manager) Option

WithInstrumentsManager installs a pre-built instruments manager so the Manager skips construction of a new one. Primary use: tests that share a seeded manager across multiple Manager instances for speed.

func WithInstrumentsSkipFetch

func WithInstrumentsSkipFetch(skip bool) Option

WithInstrumentsSkipFetch sets the test-isolation seam so the auto-created instruments manager skips the HTTP fetch of api.kite.trade/instruments.json and loads an empty instrument map instead. Ignored when WithInstrumentsManager is applied.

func WithInvitationStore

func WithInvitationStore(s *users.InvitationStore) Option

WithInvitationStore threads a pre-constructed users.InvitationStore through Manager construction. Same deprecation contract as WithAuditStore.

func WithKiteCredentials

func WithKiteCredentials(apiKey, apiSecret string) Option

WithKiteCredentials installs the Kite API key + secret pair. These two always travel together (the Kite OAuth flow needs both) so a combined setter avoids the "set key, forget secret" error class. AccessToken (optional pre-set token bypass) has its own setter because it is semantically distinct: key+secret are app identity, AccessToken is user-session identity.

func WithLogger

func WithLogger(logger *slog.Logger) Option

WithLogger sets the structured logger. Required — NewWithOptions returns an error when the assembled options have a nil Logger, matching the legacy New(cfg) contract.

func WithMetrics

func WithMetrics(m *metrics.Manager) Option

WithMetrics attaches the metrics manager used for per-user tracking and Prometheus-style counters. Optional; Manager runs without metrics when this is nil.

func WithRiskGuard

func WithRiskGuard(g *riskguard.Guard) Option

WithRiskGuard threads a pre-constructed riskguard.Guard through Manager construction. Same deprecation contract as WithAuditStore.

func WithSessionSigner

func WithSessionSigner(s *SessionSigner) Option

WithSessionSigner installs a pre-built HMAC session signer. When omitted the Manager generates a fresh signer at construction. Used by tests that need signature stability across multiple Manager instances.

func WithTelegramBotToken

func WithTelegramBotToken(token string) Option

WithTelegramBotToken configures the Telegram bot used for alert notifications. Empty token disables Telegram — the notifier simply is not constructed.

type OrderService

type OrderService struct {

	// Wave D Phase 1 Slice D2: order-write use cases.
	PlaceOrderUC  *usecases.PlaceOrderUseCase
	ModifyOrderUC *usecases.ModifyOrderUseCase
	CancelOrderUC *usecases.CancelOrderUseCase

	// Slice D3: GTT (Good Till Triggered) write use cases.
	PlaceGTTUC  *usecases.PlaceGTTUseCase
	ModifyGTTUC *usecases.ModifyGTTUseCase
	DeleteGTTUC *usecases.DeleteGTTUseCase

	// Slice D4: position-exit write use cases.
	ClosePositionUC     *usecases.ClosePositionUseCase
	CloseAllPositionsUC *usecases.CloseAllPositionsUseCase

	// Slice D5: margin-query read use cases.
	GetOrderMarginsUC  *usecases.GetOrderMarginsUseCase
	GetBasketMarginsUC *usecases.GetBasketMarginsUseCase
	GetOrderChargesUC  *usecases.GetOrderChargesUseCase

	// Slice D6: widget-query read use cases (the 2 hoistable ones).
	// GetOrdersForWidgetUC + GetActivityForWidgetUC stay per-dispatch
	// per their ctx-bound audit-store override contract.
	GetPortfolioForWidgetUC *usecases.GetPortfolioForWidgetUseCase
	GetAlertsForWidgetUC    *usecases.GetAlertsForWidgetUseCase
	// contains filtered or unexported fields
}

OrderService owns order placement, modification, and cancellation. It resolves a broker.Client per user and delegates to the broker interface. Extracted from Manager as part of Clean Architecture / SOLID refactoring.

Tier B Step 2 (2026-05-16): absorbed the 13 Wave D Phase 1 use-case fields that previously lived on Manager (placeOrderUC, modifyOrderUC, cancelOrderUC, placeGTTUC, modifyGTTUC, deleteGTTUC, closePositionUC, closeAllPositionsUC, getOrderMarginsUC, getBasketMarginsUC, getOrderChargesUC, getPortfolioForWidgetUC, getAlertsForWidgetUC). Manager goes 63 → 50 fields by this move. The constructor signature stays narrow (sessionSvc + logger); the additional 13 UCs are wired in a separate InitUseCases() call from Manager.initOrderUseCases after riskGuard / eventDispatcher / eventStore / instruments are available. This two-phase init matches the existing Wave D pattern.

func NewOrderService

func NewOrderService(sessionSvc *SessionService, logger *slog.Logger) *OrderService

NewOrderService creates a new OrderService.

func (*OrderService) CancelOrder

func (os *OrderService) CancelOrder(email, orderID, variety string) (broker.OrderResponse, error)

CancelOrder cancels an existing pending order.

func (*OrderService) GetOrders

func (os *OrderService) GetOrders(email string) ([]broker.Order, error)

GetOrders returns all orders for the user's current trading day.

func (*OrderService) GetTrades

func (os *OrderService) GetTrades(email string) ([]broker.Trade, error)

GetTrades returns all executed trades for the user's current trading day.

func (*OrderService) InitUseCases added in v0.1.3

func (os *OrderService) InitUseCases(
	riskGuard *riskguard.Guard,
	dispatcher *domain.EventDispatcher,
	eventStore usecases.EventAppender,
	lotSizeLookup usecases.LotSizeLookup,
	alertStore usecases.WidgetAlertStore,
)

InitUseCases wires the 13 absorbed Wave D use cases into OrderService. Called from Manager.initOrderUseCases after riskGuard / eventDispatcher / eventStore / instruments are available (post initFocusedServices and pre registerCQRSHandlers). All parameters are nil-tolerant per the pre-existing Wave D contract — riskGuard may be nil (DEV_MODE or no SQLite), dispatcher is nil at this point (production wires after kc.NewWithOptions; tests usually don't wire one), eventStore may be nil, instruments may be nil in test fixtures.

The construction sequence mirrors the prior manager_use_cases.go initOrderUseCases body verbatim. No behavior change.

func (*OrderService) ModifyOrder

func (os *OrderService) ModifyOrder(email, orderID string, params broker.OrderParams) (broker.OrderResponse, error)

ModifyOrder modifies an existing pending order.

func (*OrderService) PlaceOrder

func (os *OrderService) PlaceOrder(email string, params broker.OrderParams) (broker.OrderResponse, error)

PlaceOrder places a new order for the user.

func (*OrderService) PropagateDispatcher added in v0.1.3

func (os *OrderService) PropagateDispatcher(d *domain.EventDispatcher)

PropagateDispatcher fans the dispatcher into the 8 dispatch-aware use cases. Called by EventingService.SetDispatcher when production (app/wire.go) wires the real dispatcher AFTER kc.NewWithOptions has returned. Each call is nil-safe per the use case's SetEventDispatcher contract; the inner UC field-nil check guards against the pre- InitUseCases path.

type PaperEngineInterface

type PaperEngineInterface interface {
	// IsEnabled returns whether paper trading is enabled for the given email.
	IsEnabled(email string) bool

	// Enable activates paper trading with the specified initial cash.
	Enable(email string, initialCash float64) error

	// Disable deactivates paper trading.
	Disable(email string) error

	// Reset clears all paper trading data and resets cash.
	Reset(email string) error

	// Status returns a summary of the paper trading account.
	Status(email string) (map[string]any, error)

	// PlaceOrder places a paper order. Returns a Kite-compatible response map.
	PlaceOrder(email string, params map[string]any) (map[string]any, error)

	// ModifyOrder modifies an open paper order.
	ModifyOrder(email, orderID string, params map[string]any) (map[string]any, error)

	// CancelOrder cancels an open paper order.
	CancelOrder(email, orderID string) (map[string]any, error)

	// GetOrders returns all paper orders in Kite API format.
	GetOrders(email string) (any, error)

	// GetPositions returns all paper positions in Kite API format.
	GetPositions(email string) (any, error)

	// GetHoldings returns all paper holdings in Kite API format.
	GetHoldings(email string) (any, error)

	// GetMargins returns paper margin info in Kite API format.
	GetMargins(email string) (any, error)
}

PaperEngineInterface defines operations for virtual portfolio trading, allowing users to practice without real money.

type PaperEngineProvider

type PaperEngineProvider interface {
	PaperEngine() PaperEngineInterface
}

PaperEngineProvider exposes the paper trading engine. Returns nil if disabled.

type PortfolioService

type PortfolioService struct {
	// contains filtered or unexported fields
}

PortfolioService owns portfolio queries: holdings, positions, margins, profile. It resolves a broker.Client per user and delegates to the broker interface. Extracted from Manager as part of Clean Architecture / SOLID refactoring.

func NewPortfolioService

func NewPortfolioService(sessionSvc *SessionService, logger *slog.Logger) *PortfolioService

NewPortfolioService creates a new PortfolioService.

func (*PortfolioService) GetHoldings

func (ps *PortfolioService) GetHoldings(email string) ([]broker.Holding, error)

GetHoldings returns the user's stock holdings.

func (*PortfolioService) GetMargins

func (ps *PortfolioService) GetMargins(email string) (broker.Margins, error)

GetMargins returns the user's account margins.

func (*PortfolioService) GetPositions

func (ps *PortfolioService) GetPositions(email string) (broker.Positions, error)

GetPositions returns the user's current positions.

func (*PortfolioService) GetProfile

func (ps *PortfolioService) GetProfile(email string) (broker.Profile, error)

GetProfile returns the user's profile information.

type QueryBusProvider

type QueryBusProvider interface {
	QueryBus() *cqrs.InMemoryBus
}

QueryBusProvider exposes the CQRS query bus for read dispatches. Paired with CommandBusProvider; most tool handlers will depend on both because the same closure often issues a command then a confirming query.

type RawCredentialEntry

type RawCredentialEntry struct {
	Email     string
	APIKey    string
	APISecret string
}

RawCredentialEntry is an unredacted credential entry used for internal operations like backfill.

type RegistryReader

type RegistryReader interface {
	// Get retrieves a registration by ID.
	Get(id string) (*registry.AppRegistration, bool)

	// GetByAPIKey finds an active registration by API key.
	GetByAPIKey(apiKey string) (*registry.AppRegistration, bool)

	// GetByAPIKeyAnyStatus finds a registration by API key regardless of status.
	GetByAPIKeyAnyStatus(apiKey string) (*registry.AppRegistration, bool)

	// GetByEmail finds the most recently updated active registration for an email.
	GetByEmail(email string) (*registry.AppRegistration, bool)

	// List returns a redacted summary of all registered apps.
	List() []registry.AppRegistrationSummary

	// Count returns the number of registry entries.
	Count() int

	// HasEntries returns true if the registry has any entries.
	HasEntries() bool
}

RegistryReader provides read-only access to registry data.

type RegistryStoreInterface

type RegistryStoreInterface interface {
	RegistryReader
	RegistryWriter
}

RegistryStoreInterface defines operations for the Kite app key registry, used for zero-config onboarding where admins pre-register API credentials. Composed from focused sub-interfaces.

type RegistryStoreProvider

type RegistryStoreProvider interface {
	RegistryStore() RegistryStoreInterface
}

RegistryStoreProvider exposes the key registry store.

type RegistryWriter

type RegistryWriter interface {
	// Register adds a new app registration.
	Register(reg *registry.AppRegistration) error

	// Update modifies a registration's mutable fields.
	Update(id string, assignedTo, label, status string) error

	// UpdateLastUsedAt records the most recent token exchange for an API key.
	UpdateLastUsedAt(apiKey string)

	// MarkStatus sets the status of a registration found by API key.
	MarkStatus(apiKey, status string)

	// Delete removes a registration by ID.
	Delete(id string) error
}

RegistryWriter provides write operations on registry data.

type RiskGuardProvider

type RiskGuardProvider interface {
	RiskGuard() *riskguard.Guard
}

RiskGuardProvider exposes the risk guard. Returns nil if disabled.

type SchedulingService

type SchedulingService struct {
	// contains filtered or unexported fields
}

SchedulingService groups background scheduling and cleanup concerns: session cleanup routines, the Kite session cleanup hook, and operational metrics recording (which drives internal daily/cleanup routines on the metrics manager). Manager holds a *SchedulingService field and exposes thin delegators so existing callers continue to work.

Tier 1.3 (Path A.28 — final facade back-pointer elimination, follows Tier 1.1 brokers + Tier 1.2 eventing): the back-pointer to *Manager has been replaced with closures over the underlying fields. Each closure dereferences the source-of-truth Manager field at call time, preserving observable behaviour identical to the prior `s.m.X` access.

initialize() needs the closure-with-write-back variant for sessionManager because the registry is constructed inside the service then handed back to Manager as the source of truth. Read-only closures (Logger, SessionSvc, metrics) follow the same closure-by-reference idiom that landed in Tier 1.1 + 1.2.

func (*SchedulingService) CleanupExpiredSessions

func (s *SchedulingService) CleanupExpiredSessions() int

CleanupExpiredSessions manually triggers cleanup of expired MCP sessions.

func (*SchedulingService) HasMetrics

func (s *SchedulingService) HasMetrics() bool

HasMetrics returns true if a metrics manager is available.

func (*SchedulingService) IncrementDailyMetric

func (s *SchedulingService) IncrementDailyMetric(key string)

IncrementDailyMetric increments a daily metric counter by 1.

func (*SchedulingService) IncrementMetric

func (s *SchedulingService) IncrementMetric(key string)

IncrementMetric increments a metric counter by 1.

func (*SchedulingService) StopCleanupRoutine

func (s *SchedulingService) StopCleanupRoutine()

StopCleanupRoutine stops the background cleanup routine.

func (*SchedulingService) TrackDailyUser

func (s *SchedulingService) TrackDailyUser(userID string)

TrackDailyUser records a unique user interaction for today's counter.

type SessionDB

type SessionDB interface {
	SaveSession(sessionID, email string, createdAt, expiresAt time.Time, terminated bool) error
	LoadSessions() ([]*SessionLoadEntry, error)
	DeleteSession(sessionID string) error
}

SessionDB is an optional persistence backend for MCP sessions. Implemented by an adapter that wraps alerts.DB to avoid circular imports.

type SessionIdManagerShim

type SessionIdManagerShim interface {
	Generate() string
	Validate(sessionID string) (isTerminated bool, err error)
	Terminate(sessionID string) (isNotAllowed bool, err error)
}

SessionIdManagerShim mirrors mcp-go's SessionIdManager interface without importing the mcp-go server package (which would create an import cycle between kc and mcp-go). The app-level wire code in app/http.go casts this shim to the library's interface; they're structurally identical.

type SessionLifecycleService

type SessionLifecycleService struct {
	// contains filtered or unexported fields
}

SessionLifecycleService is a thin facade over SessionService that groups MCP-session lifecycle delegators (get/create/clear/complete). Manager keeps the old method names as delegators into this facade for backward compatibility with existing call sites.

func (*SessionLifecycleService) ClearSession

func (s *SessionLifecycleService) ClearSession(sessionID string)

ClearSession terminates a session, triggering cleanup hooks.

func (*SessionLifecycleService) ClearSessionData

func (s *SessionLifecycleService) ClearSessionData(sessionID string) error

ClearSessionData clears the session data without terminating the session.

func (*SessionLifecycleService) CompleteSession

func (s *SessionLifecycleService) CompleteSession(mcpSessionID, kiteRequestToken string) error

CompleteSession completes Kite authentication using the request token. Back-compat wrapper around CompleteSessionAndRotate.

func (*SessionLifecycleService) CompleteSessionAndRotate

func (s *SessionLifecycleService) CompleteSessionAndRotate(mcpSessionID, kiteRequestToken string) (string, error)

CompleteSessionAndRotate completes Kite authentication AND rotates the MCP session ID for OWASP A07 (Session Fixation) defence. Returns the post-rotation session ID.

func (*SessionLifecycleService) GenerateSession

func (s *SessionLifecycleService) GenerateSession() string

GenerateSession creates a new MCP session and returns its ID.

func (*SessionLifecycleService) GetActiveSessionCount

func (s *SessionLifecycleService) GetActiveSessionCount() int

GetActiveSessionCount returns the number of active sessions.

func (*SessionLifecycleService) GetOrCreateSession

func (s *SessionLifecycleService) GetOrCreateSession(mcpSessionID string) (*KiteSessionData, bool, error)

GetOrCreateSession retrieves an existing Kite session or creates a new one.

func (*SessionLifecycleService) GetOrCreateSessionWithEmail

func (s *SessionLifecycleService) GetOrCreateSessionWithEmail(mcpSessionID, email string) (*KiteSessionData, bool, error)

GetOrCreateSessionWithEmail retrieves or creates a Kite session with email context.

func (*SessionLifecycleService) GetSession

func (s *SessionLifecycleService) GetSession(mcpSessionID string) (*KiteSessionData, error)

GetSession retrieves an existing Kite session by MCP session ID.

func (*SessionLifecycleService) SessionLoginURL

func (s *SessionLifecycleService) SessionLoginURL(mcpSessionID string) (string, error)

SessionLoginURL returns the Kite login URL for the given session.

type SessionLoadEntry

type SessionLoadEntry struct {
	SessionID  string
	Email      string
	CreatedAt  time.Time
	ExpiresAt  time.Time
	Terminated bool
}

SessionLoadEntry represents a session loaded from the database.

type SessionRegistry

type SessionRegistry struct {
	// contains filtered or unexported fields
}

func NewSessionRegistry

func NewSessionRegistry(logger *slog.Logger) *SessionRegistry

NewSessionRegistry creates a new registry that manages MCP sessions and their associated Kite data

func NewSessionRegistryWithDuration

func NewSessionRegistryWithDuration(duration time.Duration, logger *slog.Logger) *SessionRegistry

NewSessionRegistryWithDuration creates a new session registry with custom duration

func (*SessionRegistry) AddCleanupHook

func (sm *SessionRegistry) AddCleanupHook(hook CleanupHook)

AddCleanupHook adds a cleanup function for the Kite session to be called when MCP sessions are terminated

func (*SessionRegistry) CleanupExpiredSessions

func (sm *SessionRegistry) CleanupExpiredSessions() int

CleanupExpiredSessions removes expired MCP sessions from memory and their associated Kite data

func (*SessionRegistry) Generate

func (sm *SessionRegistry) Generate() string

Generate creates a new MCP session ID and stores it in memory

func (*SessionRegistry) GenerateWithData

func (sm *SessionRegistry) GenerateWithData(data any) string

GenerateWithData creates a new MCP session ID with associated Kite data and stores it in memory. ClientHint is left empty. Callers with access to the originating HTTP request should use GenerateWithDataAndHint instead.

func (*SessionRegistry) GenerateWithDataAndHint

func (sm *SessionRegistry) GenerateWithDataAndHint(data any, clientHint string) string

GenerateWithDataAndHint creates a new MCP session ID with associated Kite data and a free-form client hint (typically the User-Agent of the HTTP request that caused the session to be issued). The hint is best-effort — empty is acceptable and renders as "Unknown" to consumers.

func (*SessionRegistry) GetOrCreateSessionData

func (sm *SessionRegistry) GetOrCreateSessionData(sessionID string, createDataFn func() any) (data any, isNew bool, err error)

GetOrCreateSessionData atomically validates session and retrieves/creates data to eliminate TOCTOU races

func (*SessionRegistry) GetSession

func (sm *SessionRegistry) GetSession(sessionID string) (*MCPSession, error)

GetSession retrieves a MCP session by ID (helper method)

func (*SessionRegistry) GetSessionData

func (sm *SessionRegistry) GetSessionData(sessionID string) (any, error)

GetSessionData retrieves the Kite data for a MCP session

func (*SessionRegistry) GetSessionDuration

func (sm *SessionRegistry) GetSessionDuration() time.Duration

GetSessionDuration returns the configured MCP session duration

func (*SessionRegistry) ListActiveSessions

func (sm *SessionRegistry) ListActiveSessions() []*MCPSession

ListActiveSessions returns all non-terminated MCP sessions (helper method)

func (*SessionRegistry) LoadFromDB

func (sm *SessionRegistry) LoadFromDB() error

LoadFromDB populates the in-memory session registry from the database. Expired and terminated sessions are skipped (and deleted from DB). Valid sessions are restored with Data set to &KiteSessionData{Email: email}.

func (*SessionRegistry) SetDB

func (sm *SessionRegistry) SetDB(db SessionDB)

SetDB enables write-through persistence to the given session database.

func (*SessionRegistry) SetSessionDuration

func (sm *SessionRegistry) SetSessionDuration(duration time.Duration)

SetSessionDuration updates the MCP session duration for new sessions

func (*SessionRegistry) StartCleanupRoutine

func (sm *SessionRegistry) StartCleanupRoutine(ctx context.Context)

StartCleanupRoutine starts background cleanup goroutines for expired MCP sessions

func (*SessionRegistry) StopCleanupRoutine

func (sm *SessionRegistry) StopCleanupRoutine()

StopCleanupRoutine stops background cleanup goroutines for MCP sessions and waits for the cleanupRoutine goroutine to exit before returning. Safe to call multiple times — the sync.Once guard absorbs double-close of the internal cancel context. Waiting (rather than returning after a signal) lets goleak-style sentinels observe the goroutine has actually terminated.

func (*SessionRegistry) Terminate

func (sm *SessionRegistry) Terminate(sessionID string) (isNotAllowed bool, err error)

Terminate marks a MCP session ID as terminated and cleans up associated Kite session

func (*SessionRegistry) TerminateByEmail

func (sm *SessionRegistry) TerminateByEmail(email string) int

TerminateByEmail terminates all active sessions belonging to the given email. Used when offboarding a user to revoke all their sessions.

func (*SessionRegistry) UpdateSessionData

func (sm *SessionRegistry) UpdateSessionData(sessionID string, data any) error

UpdateSessionData updates the Kite data for an existing MCP session

func (*SessionRegistry) UpdateSessionField

func (sm *SessionRegistry) UpdateSessionField(sessionID string, mutator func(data any)) error

UpdateSessionField updates a field within the session data under the registry lock. The mutator function is called with the session's Data pointer while holding the write lock, ensuring no concurrent reads or writes can race on session fields.

func (*SessionRegistry) Validate

func (sm *SessionRegistry) Validate(sessionID string) (isTerminated bool, err error)

Validate checks if a MCP session ID is valid and not terminated. Uses a read lock for the common (non-expired) path and only upgrades to a write lock when the session needs to be marked as terminated.

type SessionService

type SessionService struct {
	CredentialSvc *CredentialService
	// contains filtered or unexported fields
}

SessionService owns MCP session lifecycle: creation, retrieval, validation, login URL generation, and session completion. Extracted from Manager as part of Clean Architecture / SOLID refactoring.

Dependencies use interface types (Dependency Inversion Principle), enabling mock injection for testing.

func NewSessionService

func NewSessionService(cfg SessionServiceConfig) *SessionService

NewSessionService creates a new SessionService. The SessionRegistry is created internally; use SetDB to wire persistence.

func (*SessionService) CleanupExpiredSessions

func (ss *SessionService) CleanupExpiredSessions() int

CleanupExpiredSessions manually triggers cleanup of expired MCP sessions.

func (*SessionService) ClearSession

func (ss *SessionService) ClearSession(sessionID string)

ClearSession terminates a session, triggering cleanup hooks.

func (*SessionService) ClearSessionData

func (ss *SessionService) ClearSessionData(sessionID string) error

ClearSessionData clears the session data without terminating the session.

func (*SessionService) CompleteSession

func (ss *SessionService) CompleteSession(mcpSessionID, kiteRequestToken string) error

CompleteSession completes Kite authentication using the request token.

Back-compat shim around CompleteSessionAndRotate. New callers should use CompleteSessionAndRotate directly to receive the post-auth rotated session ID — that's the OWASP A07 (session fixation) defence. This wrapper discards the new ID and returns only the error so the pre-G99 `error`-only signature continues to compile.

In-tree callers (the HTTP callback handler in kc/callback_handler.go) have been migrated; the handful of test fixtures that still call this method don't care about the rotated ID.

func (*SessionService) CompleteSessionAndRotate

func (ss *SessionService) CompleteSessionAndRotate(mcpSessionID, kiteRequestToken string) (string, error)

CompleteSessionAndRotate completes Kite authentication AND rotates the MCP session ID — OWASP A07 (Session Fixation) defence.

The flow:

  1. Validate the supplied (possibly attacker-influenced) session ID.
  2. Exchange Kite request token for the access token.
  3. Generate a fresh session ID via SessionRegistry.Generate (which uses crypto/rand-backed UUIDv4 — same RNG as the original session-issuance path).
  4. Migrate the Kite session data (token, email, user id) onto the new ID.
  5. Terminate the old ID so Validate() rejects it.

The new ID is returned to the caller, which is responsible for surfacing it back to the legitimate MCP client (e.g. via a signed cookie on the callback response, or a server-side mapping keyed by the user's email).

On any pre-rotation failure the OLD session is left untouched — nothing was authenticated, nothing to invalidate. Callers seeing a non-nil error should NOT treat the old ID as terminated.

func (*SessionService) GenerateSession

func (ss *SessionService) GenerateSession() string

GenerateSession creates a new MCP session with Kite data and returns the session ID.

func (*SessionService) GetActiveSessionCount

func (ss *SessionService) GetActiveSessionCount() int

GetActiveSessionCount returns the number of active sessions.

func (*SessionService) GetBrokerForEmail

func (ss *SessionService) GetBrokerForEmail(email string) (broker.Client, error)

GetBrokerForEmail resolves a broker.Client for the given email. It first checks for an active session with a broker (preserves custom base URI and avoids creating redundant clients). In DevMode, returns a mock broker. Otherwise, falls back to creating a new client from cached credentials.

func (*SessionService) GetOrCreateSession

func (ss *SessionService) GetOrCreateSession(mcpSessionID string) (*KiteSessionData, bool, error)

GetOrCreateSession retrieves an existing Kite session or creates a new one atomically. For email-aware session creation (Fly.io with OAuth), use GetOrCreateSessionWithEmail.

func (*SessionService) GetOrCreateSessionWithEmail

func (ss *SessionService) GetOrCreateSessionWithEmail(mcpSessionID, email string) (*KiteSessionData, bool, error)

GetOrCreateSessionWithEmail retrieves or creates a Kite session with email context. If email is provided and a cached Kite token exists for that email, it is auto-applied.

func (*SessionService) GetSession

func (ss *SessionService) GetSession(mcpSessionID string) (*KiteSessionData, error)

GetSession retrieves an existing Kite session by MCP session ID.

func (*SessionService) HasBrokerFactory

func (ss *SessionService) HasBrokerFactory() bool

HasBrokerFactory reports whether an explicit broker.Factory is wired. Used by /healthz?probe=deep to surface mis-wired deployments where session creation would fail later under load. A nil factory is fine in DevMode (the session lifecycle falls through to a lazy default); production deployments should always wire one.

func (*SessionService) InitializeSessionManager

func (ss *SessionService) InitializeSessionManager()

InitializeSessionManager creates and configures the session registry with cleanup hooks.

func (*SessionService) SessionLoginURL

func (ss *SessionService) SessionLoginURL(mcpSessionID string) (string, error)

SessionLoginURL returns the Kite login URL for the given session. Returns an error in DEV_MODE since there is no real Kite client to generate a login URL.

func (*SessionService) SessionManager

func (ss *SessionService) SessionManager() *SessionRegistry

SessionManager returns the underlying SessionRegistry.

func (*SessionService) SetAuditStore

func (ss *SessionService) SetAuditStore(store AuditStoreInterface)

SetAuditStore wires the audit store for synthetic session events.

func (*SessionService) SetBrokerFactory

func (ss *SessionService) SetBrokerFactory(f broker.Factory)

SetBrokerFactory overrides the broker factory used by GetBrokerForEmail. Intended for tests that need to inject a mock broker.Factory.

func (*SessionService) SetEventDispatcher

func (ss *SessionService) SetEventDispatcher(d *domain.EventDispatcher)

SetEventDispatcher wires the domain event dispatcher so the service can emit SessionCreatedEvent on every new MCP session. Optional — a nil dispatcher disables the side-effect (existing behavior). Called from the Manager after its dispatcher is built.

func (*SessionService) SetSessionManager

func (ss *SessionService) SetSessionManager(sm *SessionRegistry)

SetSessionManager allows injecting an already-configured SessionRegistry (used by Manager).

func (*SessionService) StopCleanupRoutine

func (ss *SessionService) StopCleanupRoutine()

StopCleanupRoutine stops the background cleanup routine.

type SessionServiceConfig

type SessionServiceConfig struct {
	CredentialSvc *CredentialService
	TokenStore    TokenStoreInterface
	SessionSigner *SessionSigner
	AuditStore    AuditStoreInterface
	Logger        *slog.Logger
	Metrics       metricsTracker
	DevMode       bool
	BrokerFactory broker.Factory // optional: defaults to Zerodha factory if nil
}

SessionServiceConfig holds dependencies for creating a SessionService.

type SessionSigner

type SessionSigner struct {
	// contains filtered or unexported fields
}

SessionSigner handles HMAC signing and verification of session parameters

func NewSessionSigner

func NewSessionSigner() (*SessionSigner, error)

NewSessionSigner creates a new session signer with a random secret key

func NewSessionSignerWithKey

func NewSessionSignerWithKey(secretKey []byte) (*SessionSigner, error)

NewSessionSignerWithKey creates a new session signer with a provided secret key

func (*SessionSigner) SetSignatureExpiry

func (s *SessionSigner) SetSignatureExpiry(duration time.Duration)

SetSignatureExpiry sets the expiry duration for signed session parameters

func (*SessionSigner) SignRedirectParams

func (s *SessionSigner) SignRedirectParams(sessionID string) (string, error)

SignRedirectParams creates a signed redirect parameter string for Kite authentication

func (*SessionSigner) SignSessionID

func (s *SessionSigner) SignSessionID(sessionID string) string

SignSessionID creates a signed session parameter with timestamp and HMAC signature

func (*SessionSigner) ValidateSessionID

func (s *SessionSigner) ValidateSessionID(sessionID string) error

ValidateSessionID performs basic validation on a session ID format

func (*SessionSigner) VerifyRedirectParams

func (s *SessionSigner) VerifyRedirectParams(redirectParams string) (string, error)

VerifyRedirectParams verifies signed redirect parameters and extracts the session ID

func (*SessionSigner) VerifySessionID

func (s *SessionSigner) VerifySessionID(signedParam string) (string, error)

VerifySessionID verifies a signed session parameter and extracts the session ID

type StoreAccessor

type StoreAccessor interface {
	TokenStoreProvider
	CredentialStoreProvider
	TelegramStoreProvider
	WatchlistStoreProvider
	UserStoreProvider
	RegistryStoreProvider
	AuditStoreProvider
	BillingStoreProvider
	TickerServiceProvider
	PaperEngineProvider

	// AlertStore() + AlertDB() + InstrumentsManager() inlined here
	// (Phase B/D F1 close): the previous AlertStoreProvider /
	// AlertDBProvider / InstrumentsManagerProvider single-method
	// aliases were deleted in favor of ports.AlertPort + ports.
	// InstrumentPort at consumer sites. StoreAccessor lives in package
	// kc and cannot embed kc/ports types without creating a cycle
	// (kc/ports already imports kc), so each accessor is inlined here
	// directly. *kc.Manager satisfies the corresponding ports via its
	// existing method implementations — see kc/ports/alert.go:25,
	// kc/ports/instrument.go:32, and kc/ports/assertions.go.
	AlertStore() AlertStoreInterface
	AlertDB() *alerts.DB
	InstrumentsManager() InstrumentManagerInterface

	RiskGuardProvider
	MCPServerProvider
}

StoreAccessor is the aggregate composition of every Manager-implemented store-provider interface, retained for consumers that legitimately need broad access (e.g. the Manager itself, admin tooling, and registration code). New code should depend on the narrowest port (kc/ports) or provider it needs instead of the aggregate.

Note: TelegramNotifier, TrailingStopManager, and PnLService are intentionally excluded — those three accessors live on AlertService (obtain them via m.AlertSvc().TelegramNotifier() etc.) and are not exposed on Manager itself after the Round 3 decomposition. Consumers that need them depend on ports.AlertPort directly (which mirrors AlertService's surface), not on StoreAccessor.

type StoreRegistry

type StoreRegistry struct {
	// contains filtered or unexported fields
}

StoreRegistry groups all persistence store accessors that Manager previously exposed directly. Manager retains a *StoreRegistry field and thin delegator methods (defined below) forward to it for backward compatibility with the 73 files that already depend on the Manager-level accessors.

Concrete vs interface-typed accessor pairing -------------------------------------------- Each store has two accessors: an interface-typed one (e.g., TokenStore() → TokenStoreInterface) and a *Concrete()-suffixed sibling (e.g., TokenStoreConcrete() → *KiteTokenStore). The interface-typed pair is the canonical "consumer" surface — application logic that depends on a store should accept the narrowest interface that lets it work, per ISP and the hexagonal port discipline.

The *Concrete() siblings are an architectural escape hatch retained **for construction-site use only** — composition-root files that pass concrete pointer types into adapter struct literals or service constructors that legitimately need access to fields/methods not on the interface surface. Examples: app/app.go's kiteExchangerAdapter (needs *KiteTokenStore for direct cache access on a CQRS-bypass-ok read path), app/wire.go's briefingTokenAdapter, kc/ops/handler.go (UI handler that needs *registry.Store for List() shape — wider than RegistryReader.List() can express). Phase 3a kc/-side migration documented this status; the *Concrete() methods are not "leaks" to be retired — they are an architectural exception, symmetric with the mcp/-consumer side's admin_baseline_tool.go / admin_cache_info_tool.go AuditStoreConcrete uses for forensics-only methods (UserOrderStats / StatsCacheHitRate).

Phase 3a-complete state at this commit:

  • Consumer surfaces (mcp/, kc/telegram, kc/manager_use_cases.go's read paths) route through interface-typed accessors.
  • Construction-site surfaces (app/app.go, app/wire.go, kc/ops/handler.go, and kc-internal manager_queries_escapes.go's audit fallback) use *Concrete() siblings explicitly because they need concrete-type access for adapter constructions.

Future deletion of the *Concrete() siblings would require either (a) widening every store interface to absorb every concrete-only method (UserOrderStats, StatsCacheHitRate, etc.) — anti-Go-idiom, surface bloat, fails ISP, or (b) restructuring the construction-site adapters to take interface fields and threading the concrete-method calls through their own adapter ports — substantial app/wire.go churn that ADR 0006 §"What was rejected" explicitly excluded for ROI reasons. Both options are anti-rec'd; the current state is the calibrated end-state.

func (*StoreRegistry) AlertDB

func (s *StoreRegistry) AlertDB() *alerts.DB

AlertDB returns the optional SQLite database used for persistence.

func (*StoreRegistry) AlertStore

func (s *StoreRegistry) AlertStore() AlertStoreInterface

AlertStore returns the per-user alert store.

func (*StoreRegistry) AlertStoreConcrete

func (s *StoreRegistry) AlertStoreConcrete() *alerts.Store

AlertStoreConcrete returns the concrete alert store.

func (*StoreRegistry) AuditStore

func (s *StoreRegistry) AuditStore() AuditStoreInterface

AuditStore returns the audit trail store, or nil if not configured.

func (*StoreRegistry) AuditStoreConcrete

func (s *StoreRegistry) AuditStoreConcrete() *audit.Store

AuditStoreConcrete returns the concrete audit store.

func (*StoreRegistry) BillingStore

func (s *StoreRegistry) BillingStore() BillingStoreInterface

BillingStore returns the billing store, or nil if not configured.

func (*StoreRegistry) BillingStoreConcrete

func (s *StoreRegistry) BillingStoreConcrete() *billing.Store

BillingStoreConcrete returns the concrete billing store.

func (*StoreRegistry) CredentialStore

func (s *StoreRegistry) CredentialStore() CredentialStoreInterface

CredentialStore returns the per-email Kite credential store.

func (*StoreRegistry) CredentialStoreConcrete

func (s *StoreRegistry) CredentialStoreConcrete() *KiteCredentialStore

CredentialStoreConcrete returns the concrete credential store.

func (*StoreRegistry) InvitationStore

func (s *StoreRegistry) InvitationStore() *users.InvitationStore

InvitationStore returns the invitation store, or nil if not configured.

func (*StoreRegistry) RegistryStore

func (s *StoreRegistry) RegistryStore() RegistryStoreInterface

RegistryStore returns the key registry store.

func (*StoreRegistry) RegistryStoreConcrete

func (s *StoreRegistry) RegistryStoreConcrete() *registry.Store

RegistryStoreConcrete returns the concrete registry store.

func (*StoreRegistry) SetAuditStore

func (s *StoreRegistry) SetAuditStore(store *audit.Store)

SetAuditStore wires the audit store.

func (*StoreRegistry) SetBillingStore

func (s *StoreRegistry) SetBillingStore(store *billing.Store)

SetBillingStore sets the billing store.

func (*StoreRegistry) SetInvitationStore

func (s *StoreRegistry) SetInvitationStore(store *users.InvitationStore)

SetInvitationStore sets the invitation store.

func (*StoreRegistry) TelegramStore

func (s *StoreRegistry) TelegramStore() TelegramStoreInterface

TelegramStore returns the per-user Telegram chat ID store.

func (*StoreRegistry) TokenStore

func (s *StoreRegistry) TokenStore() TokenStoreInterface

TokenStore returns the per-email Kite token store.

func (*StoreRegistry) TokenStoreConcrete

func (s *StoreRegistry) TokenStoreConcrete() *KiteTokenStore

TokenStoreConcrete returns the concrete token store (for internal wiring).

func (*StoreRegistry) UserStore

func (s *StoreRegistry) UserStore() UserStoreInterface

UserStore returns the user identity store.

func (*StoreRegistry) UserStoreConcrete

func (s *StoreRegistry) UserStoreConcrete() *users.Store

UserStoreConcrete returns the concrete user store.

func (*StoreRegistry) WatchlistStore

func (s *StoreRegistry) WatchlistStore() WatchlistStoreInterface

WatchlistStore returns the per-user watchlist store.

func (*StoreRegistry) WatchlistStoreConcrete

func (s *StoreRegistry) WatchlistStoreConcrete() *watchlist.Store

WatchlistStoreConcrete returns the concrete watchlist store.

type TelegramStoreInterface

type TelegramStoreInterface interface {
	// SetTelegramChatID sets the Telegram chat ID for a user.
	SetTelegramChatID(email string, chatID int64)

	// GetTelegramChatID returns the Telegram chat ID for a user.
	GetTelegramChatID(email string) (int64, bool)

	// GetEmailByChatID performs a reverse lookup from chat ID to email.
	GetEmailByChatID(chatID int64) (string, bool)

	// ListAllTelegram returns all Telegram chat ID mappings.
	ListAllTelegram() map[string]int64
}

TelegramStoreInterface defines operations for managing per-user Telegram chat ID mappings. Separated from AlertStoreInterface per Single Responsibility.

type TelegramStoreProvider

type TelegramStoreProvider interface {
	TelegramStore() TelegramStoreInterface
}

TelegramStoreProvider exposes the per-user Telegram chat ID store.

type TemplateData

type TemplateData struct {
	Title       string
	RedirectURL string // optional: used by login_success.html for auto-redirect
}

TemplateData holds data for template rendering on callback success.

type TickerServiceInterface

type TickerServiceInterface interface {
	// Start creates and starts a WebSocket ticker for the given user.
	Start(email, apiKey, accessToken string) error

	// Stop stops the ticker for the given user.
	Stop(email string) error

	// UpdateToken stops and restarts a ticker with a fresh token.
	UpdateToken(email, apiKey, accessToken string) error

	// Subscribe subscribes the user's ticker to instrument tokens.
	Subscribe(email string, tokens []uint32, mode brokerticker.Mode) error

	// Unsubscribe removes instrument tokens from the user's ticker.
	Unsubscribe(email string, tokens []uint32) error

	// GetStatus returns the current status of a user's ticker.
	GetStatus(email string) (*ticker.Status, error)

	// IsRunning returns true if a ticker is active for the given email.
	IsRunning(email string) bool

	// ListAll returns a summary of all active ticker connections.
	ListAll() []ticker.UserTickerInfo

	// Shutdown stops all running tickers.
	Shutdown()
}

TickerServiceInterface defines operations for managing per-user WebSocket ticker connections for real-time market data.

type TickerServiceProvider

type TickerServiceProvider interface {
	TickerService() TickerServiceInterface
}

TickerServiceProvider exposes the per-user WebSocket ticker service.

type TokenChangeCallback

type TokenChangeCallback func(email string, entry *KiteTokenEntry)

TokenChangeCallback is invoked when a token is stored or updated.

type TokenStoreInterface

type TokenStoreInterface interface {
	// Get retrieves a stored token for the given email.
	Get(email string) (*KiteTokenEntry, bool)

	// Set stores a token for the given email and notifies observers.
	Set(email string, entry *KiteTokenEntry)

	// Delete removes a token for the given email.
	Delete(email string)

	// OnChange registers a callback that fires when a token is stored or updated.
	OnChange(cb TokenChangeCallback)

	// ListAll returns a redacted summary of all cached tokens.
	ListAll() []KiteTokenSummary

	// Count returns the number of stored tokens.
	Count() int
}

TokenStoreInterface defines operations for caching per-user Kite access tokens so users only need to login once per day.

type TokenStoreProvider

type TokenStoreProvider interface {
	TokenStore() TokenStoreInterface
}

TokenStoreProvider exposes the per-email Kite token store.

type UserAuthChecker

type UserAuthChecker interface {
	// IsAdmin returns true if the email belongs to an active admin.
	IsAdmin(email string) bool

	// HasPassword returns true if the user has a non-empty password hash.
	HasPassword(email string) bool

	// VerifyPassword checks plaintext password against stored hash.
	VerifyPassword(email, password string) (bool, error)
}

UserAuthChecker provides authentication and authorization checks.

type UserReader

type UserReader interface {
	// Get retrieves a user by email.
	Get(email string) (*users.User, bool)

	// GetByEmail is an alias for Get.
	GetByEmail(email string) (*users.User, bool)

	// Exists returns true if a user with the given email exists.
	Exists(email string) bool

	// GetStatus returns the user's status (empty if not found).
	GetStatus(email string) string

	// GetRole returns the user's role (empty if not found).
	GetRole(email string) string

	// List returns all users as a slice.
	List() []*users.User

	// Count returns the number of registered users.
	Count() int

	// ListByAdminEmail returns all users linked to this admin.
	ListByAdminEmail(adminEmail string) []*users.User

	// EnsureUser creates a user if they don't exist, returning the user.
	EnsureUser(email, kiteUID, displayName, onboardedBy string) *users.User

	// EnsureAdmin creates or upgrades a user to admin role.
	EnsureAdmin(email string)
}

UserReader provides read-only access to user data.

type UserStoreInterface

type UserStoreInterface interface {
	UserReader
	UserWriter
	UserAuthChecker
}

UserStoreInterface defines operations for user registration, authentication, and role-based access control. Composed from focused sub-interfaces.

type UserStoreProvider

type UserStoreProvider interface {
	UserStore() UserStoreInterface
}

UserStoreProvider exposes the user identity store.

type UserWriter

type UserWriter interface {
	// Create inserts a new user. Returns error if user already exists.
	Create(u *users.User) error

	// Delete removes a user from the store.
	Delete(email string)

	// UpdateLastLogin records the current time as the user's last login.
	UpdateLastLogin(email string)

	// UpdateRole changes the user's role.
	UpdateRole(email, role string) error

	// UpdateStatus changes the user's status.
	UpdateStatus(email, status string) error

	// UpdateKiteUID sets the Kite user ID for a user.
	UpdateKiteUID(email, kiteUID string)

	// SetAdminEmail links a user to their admin for family billing.
	SetAdminEmail(email, adminEmail string) error

	// SetPasswordHash stores a bcrypt password hash for the given user.
	SetPasswordHash(email, hash string) error
}

UserWriter provides write operations on user data.

type WatchlistStoreInterface

type WatchlistStoreInterface interface {
	// CreateWatchlist creates a new named watchlist for the user.
	CreateWatchlist(email, name string) (string, error)

	// DeleteWatchlist removes a watchlist and all its items.
	DeleteWatchlist(email, watchlistID string) error

	// DeleteByEmail removes all watchlists for the given email.
	DeleteByEmail(email string)

	// ListWatchlists returns all watchlists for the given email.
	ListWatchlists(email string) []*watchlist.Watchlist

	// ItemCount returns the number of items in a watchlist.
	ItemCount(watchlistID string) int

	// AddItem adds an instrument to a watchlist.
	AddItem(email, watchlistID string, item *watchlist.WatchlistItem) error

	// RemoveItem removes an instrument from a watchlist by item ID.
	RemoveItem(email, watchlistID, itemID string) error

	// GetItems returns copies of all items in a watchlist.
	GetItems(watchlistID string) []*watchlist.WatchlistItem

	// GetAllItems returns all items across all watchlists for a user.
	GetAllItems(email string) []*watchlist.WatchlistItem

	// FindWatchlistByName returns the watchlist with the given name for the user.
	FindWatchlistByName(email, name string) *watchlist.Watchlist

	// FindItemBySymbol finds an item by exchange:tradingsymbol in a watchlist.
	FindItemBySymbol(watchlistID, exchange, tradingsymbol string) *watchlist.WatchlistItem
}

WatchlistStoreInterface defines operations for managing named instrument watchlists per user.

type WatchlistStoreProvider

type WatchlistStoreProvider interface {
	WatchlistStore() WatchlistStoreInterface
}

WatchlistStoreProvider exposes the per-user watchlist store.

Directories

Path Synopsis
internal
util
Package util holds small helper functions internal to the kite-mcp-kc module.
Package util holds small helper functions internal to the kite-mcp-kc module.
ops
shared
Package shared holds the kc/ops sub-package's foundation types — pure DTOs (OverviewData, SessionInfo, TickerData, AlertData) and the LogBuffer / TeeHandler observability primitives — extracted from kc/ops/ to enable the planned Anchor 3 fan-out (PRs 3.2 admin, 3.3 user) per .research/anchor-1-and-3-pr-design.md.
Package shared holds the kc/ops sub-package's foundation types — pure DTOs (OverviewData, SessionInfo, TickerData, AlertData) and the LogBuffer / TeeHandler observability primitives — extracted from kc/ops/ to enable the planned Anchor 3 fan-out (PRs 3.2 admin, 3.3 user) per .research/anchor-1-and-3-pr-design.md.
Package ports holds the bounded-context interfaces (hexagonal ports) that Manager satisfies.
Package ports holds the bounded-context interfaces (hexagonal ports) that Manager satisfies.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL