Seed default AI settings and notification preferences at boot
CI / ci (pull_request) Successful in 1m24s
Independent review Codex gpt-6-sol high: APPROVED (0 findings)
CI / ci (push) Successful in 1m26s

On first boot create the ai_settings row (provider anthropic, empty base
URL, model and key, temperature 0.7, system prompt DefaultPersonaPrompt
from CTXD PROMPTS.MD §4) and a notification_prefs row per event (push on
for all five, chat on for plan_ready and list_ready only). Existing rows
are never changed; a missing event row is seeded on its own.

cmd/server seeds household settings, AI settings and notification
preferences through seedDefaults; any failure stops the boot.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-03 14:54:58 -04:00
co-authored by Claude Opus 5.5
parent 8be5c56f6f
commit 989d13b69c
4 changed files with 364 additions and 1 deletions
+38
View File
@@ -330,3 +330,41 @@ func TestSeedSettingsAtBoot(t *testing.T) {
t.Errorf("timezone after reboot = %q, want America/Denver", got.Timezone)
}
}
func TestSeedDefaultsAtBoot(t *testing.T) {
ctx := context.Background()
db, err := openStore(ctx, t.TempDir())
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() {
if err := db.Close(); err != nil {
t.Error(err)
}
})
if err := seedDefaults(ctx, db, "America/Denver"); err != nil {
t.Fatal(err)
}
if _, err := household.NewSettingsService(db).Get(ctx); err != nil {
t.Errorf("household settings: %v", err)
}
var provider string
if err := db.Reader.QueryRowContext(ctx, `SELECT provider FROM ai_settings`).Scan(&provider); err != nil {
t.Errorf("AI settings: %v", err)
} else if provider != household.SeedAIProvider {
t.Errorf("AI provider = %q, want %q", provider, household.SeedAIProvider)
}
var prefs int
if err := db.Reader.QueryRowContext(ctx, `SELECT count(*) FROM notification_prefs`).Scan(&prefs); err != nil {
t.Fatal(err)
}
if prefs != 5 {
t.Errorf("%d notification preferences, want 5", prefs)
}
// A second boot seeds nothing new and succeeds.
if err := seedDefaults(ctx, db, ""); err != nil {
t.Fatalf("reboot: %v", err)
}
}
+13 -1
View File
@@ -59,7 +59,7 @@ func run(cfg config.Config, logger *slog.Logger) error {
}
}()
if err := seedSettings(ctx, db, cfg.DefaultTZ); err != nil {
if err := seedDefaults(ctx, db, cfg.DefaultTZ); err != nil {
return err
}
settings := household.NewSettingsService(db)
@@ -153,6 +153,18 @@ func seedSettings(ctx context.Context, db *store.Store, defaultTZ string) error
return err
}
// seedDefaults creates the household settings, AI settings and notification
// preferences that do not exist yet. Existing rows are never changed.
func seedDefaults(ctx context.Context, db *store.Store, defaultTZ string) error {
if err := seedSettings(ctx, db, defaultTZ); err != nil {
return err
}
if err := household.SeedAISettings(ctx, db); err != nil {
return err
}
return household.SeedNotificationPrefs(ctx, db)
}
// logListening logs the startup line. Only the public URL's origin is logged
// so no credential-bearing URL component can reach the log.
func logListening(logger *slog.Logger, cfg config.Config) {