// Package bot owns the chatbot session/scope/turn service for the // queryAPI. M2 implements session and scope CRUD; M3 adds the FastAPI // client; M4 adds AddTurn orchestration. See plans/chatbot_plan_codex.v10.md // for the canonical spec. // // Authorization boundary: this package never imports cognitoauth. The // handler extracts the Cognito subject and passes it as the actor // argument; every owner-scoped read/write filters on (client_id, // created_by, is_deleted=false). Super-admin reads filter on // (client_id, is_deleted=false). package bot import ( "fmt" "net/http" "queryorchestration/internal/serviceconfig" "queryorchestration/internal/serviceconfig/chatbot" ) // Service is the chatbot domain service. It holds the validated chatbot // configuration plus the database access provided by the embedding // QueryAPIConfig (via serviceconfig.ConfigProvider). type Service struct { cfg chatbot.ChatbotConfig dbCfg serviceconfig.ConfigProvider fastAPI *FastAPIClient } // New constructs a Service. It validates the supplied chatbot config // up-front so a caller cannot accidentally instantiate a service whose // preview turn-limit cap is out of range. The dbCfg argument is the // existing serviceconfig.ConfigProvider (queryAPI's QueryAPIConfig) used // for DB access; it may be nil in pure unit tests that exercise only // the validation gate. // // Returns the constructed Service plus a non-nil error when the config // fails Validate. Plan §8 mandates the validation here so any future // caller (CLI, integration tests, alternate composition roots) inherits // the same gate. // // The FastAPI client is constructed eagerly so M4's AddTurn path has a // ready dependency and so the WithBaseURL / WithHTTPClient options get // exercised from production code. WithBaseURL forces the client to // honor the cfg.ServiceURL even if a future refactor changes the // FastAPIClient default; WithHTTPClient passes a fresh http.Client so // the M3 retry logic can drive its own timeouts without sharing a // connection pool with unrelated callers. func New(cfg chatbot.ChatbotConfig, dbCfg serviceconfig.ConfigProvider) (*Service, error) { if err := cfg.Validate(); err != nil { return nil, fmt.Errorf("bot.New: %w", err) } fastAPI := NewFastAPIClient(cfg, WithBaseURL(cfg.ServiceURL), WithHTTPClient(&http.Client{}), ) return &Service{cfg: cfg, dbCfg: dbCfg, fastAPI: fastAPI}, nil } // MinTurnLimit returns min(reqLimit, recentTurns). Plan §3 mandates // turn_limit = min(request.limit, cfg.RecentTurns) for the session-list // preview. Exported so the service, handlers, and tests share one // implementation. Negative inputs are returned as-is so the caller can // fail closed at the validation layer rather than silently clamping. func MinTurnLimit(reqLimit, recentTurns int) int { if reqLimit < recentTurns { return reqLimit } return recentTurns }