diff --git a/.version b/.version index 425438cc..80ea31f9 100644 --- a/.version +++ b/.version @@ -1,6 +1,6 @@ { "major": 1, "minor": 8, - "patch": 21, + "patch": 22, "prerelease": "" } diff --git a/api-schema/tmi-openapi.json b/api-schema/tmi-openapi.json index 9984c3cf..109e71ab 100644 --- a/api-schema/tmi-openapi.json +++ b/api-schema/tmi-openapi.json @@ -3,7 +3,7 @@ "info": { "title": "TMI (Threat Modeling Improved) API", "description": "A RESTful API for collaborative threat modeling with full X6 graph library compatibility. This API provides schemas that align with AntV X6 cell object models for seamless integration with modern diagramming libraries. Supports OAuth 2.0 authentication with client callback integration for seamless single-page application authentication flows.\n\n## API Design v1.1.0\n\n### Authorization Model\nTMI uses hierarchical authorization: access control is defined at the ThreatModel level via the authorization field (readers, writers, owners). All child resources (Assets, Diagrams, Documents, Notes, Repositories, Threats) inherit permissions from their parent ThreatModel. This simplifies permission management and ensures consistent access control.\n\n### Bulk Operations\nNotes and Diagrams do not support bulk operations due to their unique creation workflows and lack of valid bulk use cases. All other resources (Threats, Assets, Documents, Repositories) support full bulk operations: POST (create), PUT (upsert), PATCH (partial update), DELETE (batch delete).\n\nAll resources support bulk metadata operations regardless of resource-level bulk support.\n\n### List Response Strategy\n- ThreatModels return summary information (TMListItem) because they contain many child objects that can be large.\n- Diagrams return summary information (DiagramListItem) because diagram data (cells, images) can be large.\n- Notes return summary information (NoteListItem) because the content field can be large.\n- Threats, Assets, Documents, Repositories return full schemas as they are relatively small and static.\n\n### PATCH Support\nAll resources support PATCH for partial updates using JSON Patch (RFC 6902). This is particularly useful for:\n- Assets: Array field updates (affected_assets, trust_boundaries) ensuring no duplicates\n- Notes: Updating name/description without changing content field\n- All resources: Efficient updates without full object replacement\n", - "version": "1.8.21", + "version": "1.8.22", "contact": { "name": "TMI Development Team", "url": "https://github.com/ericfitz/tmi", diff --git a/api/api.go b/api/api.go index 07fc1feb..88d2d1fa 100644 --- a/api/api.go +++ b/api/api.go @@ -25952,1089 +25952,1089 @@ var swaggerSpec = []string{ "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", } // decodeSpec returns the embedded OpenAPI spec as raw JSON bytes, diff --git a/api/config_handlers.go b/api/config_handlers.go index 4bc597bd..06bd3027 100644 --- a/api/config_handlers.go +++ b/api/config_handlers.go @@ -615,7 +615,7 @@ func (s *Server) GetSystemSetting(c *gin.Context, key string) { } // UpdateSystemSetting creates or updates a system setting (admin only) -// SEM@5dfa9dcf64aa0662920dbbab3bca200db1b22c73: create or update a database system setting, validating provider enables and invalidating cache (reads DB) +// SEM@0000000000000000000000000000000000000000: create or update a database system setting with explicit origin, validating provider enables and invalidating cache (reads DB) func (s *Server) UpdateSystemSetting(c *gin.Context, key string) { logger := slogging.Get().WithContext(c) ctx := c.Request.Context() @@ -676,7 +676,9 @@ func (s *Server) UpdateSystemSetting(c *gin.Context, key string) { } } - // Convert to model + // Convert to model. Origin is stamped explicit here (an admin deliberately + // PUT this value) and again by SettingsService.Set below, which is the + // authoritative writer — belt and suspenders (#794). setting := models.SystemSetting{ SettingKey: models.DBVarchar(key), Value: models.DBText(req.Value), @@ -684,6 +686,7 @@ func (s *Server) UpdateSystemSetting(c *gin.Context, key string) { ModifiedAt: time.Now(), ModifiedBy: models.NewNullableDBVarchar(modifiedBy), Description: models.NewNullableDBText(req.Description), + Origin: models.NullableDBVarchar{String: models.SystemSettingOriginExplicit, Valid: true}, } // Enable-validation gate: validate required fields when enabling a provider diff --git a/api/config_handlers_test.go b/api/config_handlers_test.go index 46eb525b..64f18dd8 100644 --- a/api/config_handlers_test.go +++ b/api/config_handlers_test.go @@ -46,7 +46,7 @@ func (m *MockSettingsService) GetString(ctx context.Context, key string) (string return "", nil } -func (m *MockSettingsService) GetDatabaseString(ctx context.Context, key string) (string, bool, error) { +func (m *MockSettingsService) GetResolvedString(ctx context.Context, key string) (string, bool, error) { if setting, ok := m.settings[key]; ok { return string(setting.Value), true, nil } diff --git a/api/models/system_setting.go b/api/models/system_setting.go index 5f50ba86..55b15a05 100644 --- a/api/models/system_setting.go +++ b/api/models/system_setting.go @@ -8,7 +8,7 @@ import ( // SystemSetting represents a system-wide configuration setting stored in the database. // These settings can be modified at runtime without requiring server restart. // Settings are cached with short TTL for performance. -// SEM@db6c3b75a42a48dd122e5984e9efdf0e6e15ca9d: GORM model for a runtime-configurable system setting with key, typed value, and audit fields (reads DB) +// SEM@2daf3be663df9da54323f16d115f12d78d435c3f: GORM model for a runtime-configurable system setting with key, typed value, and audit fields (reads DB) type SystemSetting struct { // SettingKey is the unique identifier for this setting (e.g., "rate_limit.requests_per_minute") // Named SettingKey instead of Key to avoid Oracle reserved word conflict @@ -20,6 +20,36 @@ type SystemSetting struct { Description NullableDBText `gorm:"" json:"description,omitempty"` ModifiedAt time.Time `gorm:"not null;autoUpdateTime" json:"modified_at"` ModifiedBy NullableDBVarchar `gorm:"size:36" json:"modified_by,omitempty"` // User InternalUUID + // Origin records who wrote this row's current value: SystemSettingOriginSeeded + // when SeedDefaults inserted the registry default at first boot, or + // SystemSettingOriginExplicit when an operator deliberately set it (via the + // admin API, SettingsService.Set, or dbtool --import-config). + // + // NULL means SEEDED — the fail-safe direction. Every way an origin value + // can go missing (an empty string bound on Oracle, a future writer that + // forgets the stamp, a stale pre-upgrade Redis entry whose cached JSON has + // no origin key) therefore degrades to "seeded", which makes the config + // layer win. Config is the layer an operator can see and control, so + // losing this value must never hand authority to a database row nobody set + // — that is precisely the 2026-08-20 outage (oracle-db-admin review, #794). + // + // Rows that predate this column are stamped explicit where they show + // operator intent, by BackfillSystemSettingOrigin in internal/dbschema. + // The column itself is added with no `ALTER TABLE ... DEFAULT` (Oracle + // rejects unquoted string defaults — see the SettingType comment above). + // Never write "" here: Oracle binds an + // empty string as NULL, so an empty string and NULL would be + // indistinguishable there. Only NULL, SystemSettingOriginSeeded, or + // SystemSettingOriginExplicit are valid values. + // Not part of the wire API: modelToAPISystemSetting (api/config_handlers.go) + // builds the API SystemSetting field by field and never copies Origin + // across, so it never reaches a client regardless of this tag. The tag is + // a real json name (not "-") because the Redis cache tier round-trips a + // SystemSetting through json.Marshal/Unmarshal (setInRedisCache / + // getFromRedisCache) — "-" would silently drop Origin on every Redis + // cache hit and make every setting look explicit after a warm read + // (oracle-db-admin review, #794). + Origin NullableDBVarchar `gorm:"size:16" json:"origin,omitempty"` // Source indicates where the effective value comes from: "database", "config", "environment", "vault" // Computed at response time, not stored in the database. Source string `gorm:"-" json:"source"` @@ -47,6 +77,28 @@ const ( SystemSettingTypeFloat = "float" ) +// SystemSettingOrigin constants for the Origin field +const ( + // SystemSettingOriginSeeded marks a row inserted by SeedDefaults with a + // registry default value — no operator has ever set it. + SystemSettingOriginSeeded = "seeded" + // SystemSettingOriginExplicit marks a row an operator deliberately set, + // via the admin API, SettingsService.Set, or dbtool --import-config. + SystemSettingOriginExplicit = "explicit" +) + +// IsExplicit reports whether an operator deliberately set this row's value, +// as opposed to it having been seeded with a registry default at first boot. +// +// Only an explicit "explicit" counts. NULL, "seeded", and any unexpected value +// all read as NOT explicit, so the database only outranks an explicitly +// configured env/YAML value when something deliberately said so. See the +// Origin field's comment for why that polarity is the safe one. +// SEM@2daf3be663df9da54323f16d115f12d78d435c3f: report whether a setting's value was deliberately set rather than seeded (pure) +func (s *SystemSetting) IsExplicit() bool { + return s.Origin.Valid && s.Origin.String == SystemSettingOriginExplicit +} + // DefaultSystemSettings returns the default system settings that should be seeded // when the database is initialized. These provide sensible defaults that can be // overridden by administrators. diff --git a/api/models/system_setting_test.go b/api/models/system_setting_test.go new file mode 100644 index 00000000..13aaf804 --- /dev/null +++ b/api/models/system_setting_test.go @@ -0,0 +1,94 @@ +package models + +import ( + "encoding/json" + "testing" +) + +// TestSystemSetting_IsExplicit pins the #794 precedence rule and, critically, +// its polarity: "explicit" is the ONLY value that yields true. NULL, "seeded", +// and anything unexpected all read as not-explicit, so the database outranks a +// configured env/YAML value only when something deliberately said so. +// +// The polarity is the safety property. Every way an origin value can go +// missing — an empty string bound on Oracle, a writer that forgets the stamp, +// a stale pre-upgrade Redis entry with no origin key — lands on "not +// explicit", which makes the config layer win. Config is what an operator can +// see and control; a row nobody set winning is the 2026-08-20 outage. +func TestSystemSetting_IsExplicit(t *testing.T) { + tests := []struct { + name string + origin NullableDBVarchar + want bool + }{ + { + name: "NULL origin is not explicit (fail-safe)", + origin: NullableDBVarchar{Valid: false}, + want: false, + }, + { + name: "seeded origin is not explicit", + origin: NullableDBVarchar{String: SystemSettingOriginSeeded, Valid: true}, + want: false, + }, + { + name: "explicit origin is explicit", + origin: NullableDBVarchar{String: SystemSettingOriginExplicit, Valid: true}, + want: true, + }, + { + name: "unexpected origin value is not explicit (fail-safe)", + origin: NullableDBVarchar{String: "some-future-value", Valid: true}, + want: false, + }, + { + name: "empty-string origin is not explicit (Oracle binds '' as NULL)", + origin: NullableDBVarchar{String: "", Valid: true}, + want: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := &SystemSetting{Origin: tt.origin} + if got := s.IsExplicit(); got != tt.want { + t.Errorf("IsExplicit() = %v, want %v", got, tt.want) + } + }) + } +} + +// TestSystemSetting_OriginJSONRoundTrip guards against Origin regressing to +// json:"-". The Redis cache tier (SettingsService.setInRedisCache / +// getFromRedisCache) round-trips a SystemSetting through json.Marshal / +// json.Unmarshal; "-" would silently drop Origin on every Redis cache hit, +// making every seeded row look explicit after a warm read (oracle-db-admin +// review, #794). This is orthogonal to the wire API, which never marshals +// models.SystemSetting directly — modelToAPISystemSetting builds the API +// type field by field and never copies Origin across. +func TestSystemSetting_OriginJSONRoundTrip(t *testing.T) { + original := SystemSetting{ + SettingKey: "session.timeout_minutes", + Value: "60", + SettingType: SystemSettingTypeInt, + Origin: NullableDBVarchar{String: SystemSettingOriginSeeded, Valid: true}, + } + + data, err := json.Marshal(original) + if err != nil { + t.Fatalf("Marshal: %v", err) + } + + var roundTripped SystemSetting + if err := json.Unmarshal(data, &roundTripped); err != nil { + t.Fatalf("Unmarshal: %v", err) + } + + if !roundTripped.Origin.Valid || roundTripped.Origin.String != SystemSettingOriginSeeded { + t.Errorf("Origin after round-trip = (valid=%v, %q), want (valid=true, %q)", + roundTripped.Origin.Valid, roundTripped.Origin.String, SystemSettingOriginSeeded) + } + if roundTripped.IsExplicit() { + t.Error("round-tripped seeded row reports IsExplicit() == true; Origin was lost in JSON marshaling") + } +} diff --git a/api/runtime_config_reader_adapter.go b/api/runtime_config_reader_adapter.go index d88f3f22..97cc4139 100644 --- a/api/runtime_config_reader_adapter.go +++ b/api/runtime_config_reader_adapter.go @@ -36,15 +36,17 @@ func NewRuntimeConfigReaderAdapter(settings SettingsServiceInterface) *RuntimeCo // - exists=true, err!=nil: DB row present but unusable → caller MUST // fail-closed to prevent open-redirect against a corrupt row. // -// The read is database-only (GetDatabaseString): for #419 runtime keys the -// DB row must win over the YAML config, which acts purely as a first-run -// fallback. Reading through GetString would invert that — its -// env/config-file priority silently shadows the DB row whenever the YAML -// also carries the key (#767). +// The read goes through GetResolvedString, which applies the converged +// precedence rule (#794). It used to be database-only, because for #419 +// runtime keys the DB row must win over the YAML — and reading through +// GetString would have inverted that, since its env/config-file priority +// shadows the DB row whenever the YAML also carries the key (#767). +// GetResolvedString keeps that property for any row an operator actually +// set, while letting an explicit config value beat a merely-seeded default. // -// SEM@08e19a77d4d2c499f116e1a1ee3c875c06407335: fetch the OAuth client callback allowlist from DB settings only; fail-closed on corrupt row (reads DB) +// SEM@2daf3be663df9da54323f16d115f12d78d435c3f: fetch the OAuth client callback allowlist by resolved precedence; fail-closed on corrupt row (reads DB) func (a *RuntimeConfigReaderAdapter) GetClientCallbackAllowList(ctx context.Context) ([]string, bool, error) { - raw, exists, err := a.settings.GetDatabaseString(ctx, "auth.oauth.client_callback_allowlist") + raw, exists, err := a.settings.GetResolvedString(ctx, "auth.oauth.client_callback_allowlist") if err != nil { // A TRANSIENT DB fault (ADB idle-session kill, pool exhaustion, // listener blip — ORA-02396/03113/12537 class) must NOT fail closed: @@ -74,14 +76,19 @@ func (a *RuntimeConfigReaderAdapter) GetClientCallbackAllowList(ctx context.Cont return list, true, nil } -// IsSAMLEnabled reads features.saml_enabled, DB row first (#419/#767). -// When no DB row exists it falls back to GetString (env/config file) — +// IsSAMLEnabled reads features.saml_enabled by the converged precedence +// rule (#794). When no layer supplies a value it falls back to GetString — // unlike the other two readers, the auth handler has no YAML fallback of // its own once a RuntimeConfigReader is wired. A read error or garbage // value returns false (fail-closed). -// SEM@08e19a77d4d2c499f116e1a1ee3c875c06407335: check whether SAML login is enabled, DB-first with config fallback; fail-closed on error (reads DB) +// +// Note that TMI_SAML_ENABLED separately gates SAML manager construction at +// startup (auth/service.go), so the env var stays load-bearing regardless of +// what this returns, and enabling SAML via the database alone still needs a +// restart. That asymmetry is called out in #794 and is not fixed here. +// SEM@2daf3be663df9da54323f16d115f12d78d435c3f: check whether SAML login is enabled by resolved precedence; fail-closed on error (reads DB) func (a *RuntimeConfigReaderAdapter) IsSAMLEnabled(ctx context.Context) bool { - raw, exists, err := a.settings.GetDatabaseString(ctx, "features.saml_enabled") + raw, exists, err := a.settings.GetResolvedString(ctx, "features.saml_enabled") if err != nil { // Transient DB faults fall through to the config-layer fallback below // (same reasoning as GetClientCallbackAllowList): an ADB blip must not @@ -110,12 +117,20 @@ func (a *RuntimeConfigReaderAdapter) IsSAMLEnabled(ctx context.Context) bool { return v } -// GetOAuthCallbackURL reads auth.oauth_callback_url from the database only -// (#419/#767). An empty string is returned on error/missing row; the -// caller falls back to the YAML snapshot in h.config.OAuth.CallbackURL. -// SEM@08e19a77d4d2c499f116e1a1ee3c875c06407335: fetch the OAuth callback URL from DB settings only; return empty string on error (reads DB) +// GetOAuthCallbackURL reads auth.oauth_callback_url by the converged +// precedence rule (#794). An empty string is returned on error or when no +// layer supplies a value; the caller falls back to the YAML snapshot in +// h.config.OAuth.CallbackURL. +// +// This is the key that broke production on 2026-08-20. RDS carried a +// registry-seeded default of http://localhost:8080/oauth2/callback, the +// database-only read let it outrank a correctly-set TMI_OAUTH_CALLBACK_URL, +// and every provider rejected the resulting redirect_uri. Under the +// converged rule a seeded row loses to an explicit env value, so that +// specific failure cannot recur. +// SEM@2daf3be663df9da54323f16d115f12d78d435c3f: fetch the OAuth callback URL by resolved precedence; return empty string on error (reads DB) func (a *RuntimeConfigReaderAdapter) GetOAuthCallbackURL(ctx context.Context) string { - raw, exists, err := a.settings.GetDatabaseString(ctx, "auth.oauth_callback_url") + raw, exists, err := a.settings.GetResolvedString(ctx, "auth.oauth_callback_url") if err != nil { slogging.Get().Warn("RuntimeConfigReader: failed to read auth.oauth_callback_url: %v", err) return "" @@ -131,5 +146,31 @@ func (a *RuntimeConfigReaderAdapter) GetOAuthCallbackURL(ctx context.Context) st return raw } +// IsEveryoneAReviewer reads auth.everyone_is_a_reviewer by the converged +// precedence rule (#794). Fail-closed: any read error, missing value, or +// unparseable value returns false. +// +// This key previously had no runtime reader at all — its only consumer read +// the config struct field directly, so the database row was display-only +// drift and editing it did nothing. It is the fifth and last of the keys +// #794 catalogued as following inconsistent precedence. +// SEM@2daf3be663df9da54323f16d115f12d78d435c3f: check whether all users are auto-added as security reviewers; fail-closed on error (reads DB) +func (a *RuntimeConfigReaderAdapter) IsEveryoneAReviewer(ctx context.Context) bool { + raw, exists, err := a.settings.GetResolvedString(ctx, "auth.everyone_is_a_reviewer") + if err != nil { + slogging.Get().Warn("RuntimeConfigReader: failed to read auth.everyone_is_a_reviewer: %v", err) + return false + } + if !exists || raw == "" { + return false + } + v, parseErr := strconv.ParseBool(raw) + if parseErr != nil { + slogging.Get().Warn("RuntimeConfigReader: auth.everyone_is_a_reviewer is not a valid bool (%q): %v", raw, parseErr) + return false + } + return v +} + // Compile-time check that the adapter satisfies the auth interface. var _ auth.RuntimeConfigReader = (*RuntimeConfigReaderAdapter)(nil) diff --git a/api/runtime_config_reader_adapter_test.go b/api/runtime_config_reader_adapter_test.go index bd2c596e..ff066bd7 100644 --- a/api/runtime_config_reader_adapter_test.go +++ b/api/runtime_config_reader_adapter_test.go @@ -12,7 +12,7 @@ import ( // fakeSettingsService is a minimal SettingsServiceInterface stub for adapter // tests. Only the methods the adapter actually calls are exercised. -// db simulates database rows (GetDatabaseString); strings simulates the +// db simulates resolved rows (GetResolvedString); strings simulates the // config layer (GetString reads ONLY this map — the maps are deliberately // disjoint so the config-fallback tests fail if the adapter's explicit // fallback read is ever removed). errs fails both reads for a key; dbErrs @@ -42,7 +42,7 @@ func (f *fakeSettingsService) GetString(ctx context.Context, key string) (string } return f.strings[key], nil } -func (f *fakeSettingsService) GetDatabaseString(ctx context.Context, key string) (string, bool, error) { +func (f *fakeSettingsService) GetResolvedString(ctx context.Context, key string) (string, bool, error) { if err, ok := f.dbErrs[key]; ok { return "", false, err } @@ -328,3 +328,54 @@ func TestRuntimeConfigReaderAdapter_TransientDBFaultFallsBackToYAML(t *testing.T } }) } + +// IsEveryoneAReviewer is the fifth key #794 catalogued. Before this change it +// had no runtime reader at all — the only consumer read the config struct +// directly, so the database row was visible, editable, and inert. +func TestRuntimeConfigReaderAdapter_IsEveryoneAReviewer(t *testing.T) { + const key = "auth.everyone_is_a_reviewer" + + tests := []struct { + name string + setup func(f *fakeSettingsService) + want bool + }{ + { + name: "resolved true", + setup: func(f *fakeSettingsService) { f.db[key] = "true" }, + want: true, + }, + { + name: "resolved false", + setup: func(f *fakeSettingsService) { f.db[key] = "false" }, + want: false, + }, + { + name: "no value anywhere is fail-closed", + setup: func(f *fakeSettingsService) {}, + want: false, + }, + { + name: "unparseable value is fail-closed", + setup: func(f *fakeSettingsService) { f.db[key] = "yes-please" }, + want: false, + }, + { + name: "read error is fail-closed", + setup: func(f *fakeSettingsService) { f.errs[key] = errors.New("boom") }, + want: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f := newFakeSettingsService() + tt.setup(f) + a := NewRuntimeConfigReaderAdapter(f) + + if got := a.IsEveryoneAReviewer(context.Background()); got != tt.want { + t.Errorf("IsEveryoneAReviewer() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/api/server.go b/api/server.go index 15876716..54ce3e98 100644 --- a/api/server.go +++ b/api/server.go @@ -20,10 +20,12 @@ import ( type SettingsServiceInterface interface { Get(ctx context.Context, key string) (*models.SystemSetting, error) GetString(ctx context.Context, key string) (string, error) - // GetDatabaseString reads from the database only (no env/config-file - // priority) and reports whether the row exists. Used by the #419 - // runtime-config path, where the DB must win over the YAML (#767). - GetDatabaseString(ctx context.Context, key string) (string, bool, error) + // GetResolvedString applies the single converged precedence rule across + // the config layer and the database, and reports whether any layer + // supplied a usable value. Used by the #419 runtime-config path. It + // replaced GetDatabaseString so the two precedence rules that caused the + // 2026-08-20 outage cannot re-diverge (#794). + GetResolvedString(ctx context.Context, key string) (string, bool, error) GetInt(ctx context.Context, key string) (int, error) GetBool(ctx context.Context, key string) (bool, error) List(ctx context.Context) ([]models.SystemSetting, error) diff --git a/api/settings_precedence_test.go b/api/settings_precedence_test.go index 28867690..5ca5ee48 100644 --- a/api/settings_precedence_test.go +++ b/api/settings_precedence_test.go @@ -1,8 +1,13 @@ package api import ( + "context" "testing" + "github.com/stretchr/testify/require" + "gorm.io/gorm" + + "github.com/ericfitz/tmi/api/models" "github.com/ericfitz/tmi/internal/config" ) @@ -118,3 +123,179 @@ func TestGetConfigSetting_UnknownKeyNotFound(t *testing.T) { t.Fatal("unknown key should not be found in the config layer") } } + +// --------------------------------------------------------------------------- +// GetResolvedString — the converged precedence rule (#794) +// --------------------------------------------------------------------------- +// +// These tests pin the four-case table that replaced the two contradictory +// rules described at the top of this file. The case that matters most is +// "explicit config + seeded DB row -> config wins": that is the exact shape of +// the 2026-08-20 production outage, where a registry-seeded +// http://localhost:8080/oauth2/callback row outranked a correctly-set +// TMI_OAUTH_CALLBACK_URL and broke OAuth login for every provider. + +// seedRow inserts a system_settings row with the given origin. An empty origin +// string writes NULL, which reads back as seeded — the fail-safe direction. +func seedRow(t *testing.T, db *gorm.DB, key, value, origin string) { + t.Helper() + row := models.SystemSetting{ + SettingKey: models.DBVarchar(key), + Value: models.DBText(value), + SettingType: models.SystemSettingTypeString, + } + if origin != "" { + row.Origin = models.NullableDBVarchar{String: origin, Valid: true} + } + require.NoError(t, db.Create(&row).Error) +} + +func TestGetResolvedString_PrecedenceTable(t *testing.T) { + const key = "auth.oauth_callback_url" + const cfgValue = "https://api.tmi.dev/oauth2/callback" + const dbValue = "http://localhost:8080/oauth2/callback" + + tests := []struct { + name string + cfgExplicit bool + dbOrigin string // "" == no row at all + wantValue string + wantExists bool + wantRationale string + }{ + { + name: "neither explicit: database wins", + cfgExplicit: false, + dbOrigin: models.SystemSettingOriginSeeded, + wantValue: dbValue, + wantExists: true, + wantRationale: "both sides are defaults; prefer the runtime-editable one", + }, + { + name: "only database explicit: database wins", + cfgExplicit: false, + dbOrigin: models.SystemSettingOriginExplicit, + wantValue: dbValue, + wantExists: true, + wantRationale: "#415: a bare struct default must yield to the database", + }, + { + name: "only config explicit: config wins", + cfgExplicit: true, + dbOrigin: models.SystemSettingOriginSeeded, + wantValue: cfgValue, + wantExists: true, + wantRationale: "the 2026-08-20 outage: a seeded default must never beat an operator", + }, + { + name: "both explicit: database wins", + cfgExplicit: true, + dbOrigin: models.SystemSettingOriginExplicit, + wantValue: dbValue, + wantExists: true, + wantRationale: "#419/#767: an admin's runtime edit takes effect without a redeploy", + }, + { + name: "row with NULL origin reads as seeded, so config wins", + cfgExplicit: true, + dbOrigin: "", + wantValue: cfgValue, + wantExists: true, + wantRationale: "fail-safe polarity: a lost origin must never hand authority to a row nobody set", + }, + { + name: "unexpected origin value also reads as seeded", + cfgExplicit: true, + dbOrigin: "banana", + wantValue: cfgValue, + wantExists: true, + wantRationale: "only a deliberate 'explicit' counts; anything else degrades safely", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + db := setupSettingsTestDB(t) + seedRow(t, db, key, dbValue, tt.dbOrigin) + + s := NewSettingsService(db, nil) + s.SetConfigProvider(&stubConfigProvider{settings: []MigratableSetting{ + operationalSetting(key, cfgValue, "environment", tt.cfgExplicit), + }}) + + got, exists, err := s.GetResolvedString(context.Background(), key) + require.NoError(t, err) + require.Equal(t, tt.wantExists, exists) + require.Equalf(t, tt.wantValue, got, "rationale: %s", tt.wantRationale) + }) + } +} + +// No row at all: the config layer is the only source, explicit or not. +func TestGetResolvedString_NoDatabaseRowFallsBackToConfig(t *testing.T) { + db := setupSettingsTestDB(t) + s := NewSettingsService(db, nil) + s.SetConfigProvider(&stubConfigProvider{settings: []MigratableSetting{ + operationalSetting("timmy.enabled", "true", "config", false), + }}) + + got, exists, err := s.GetResolvedString(context.Background(), "timmy.enabled") + require.NoError(t, err) + require.True(t, exists) + require.Equal(t, "true", got) +} + +// An empty row value is treated as absent. On Oracle ” binds as NULL, so an +// empty-but-present row is indistinguishable from a missing one; the rule must +// not depend on which engine is underneath. +func TestGetResolvedString_EmptyRowTreatedAsAbsent(t *testing.T) { + const key = "auth.oauth_callback_url" + db := setupSettingsTestDB(t) + seedRow(t, db, key, "", models.SystemSettingOriginExplicit) + + s := NewSettingsService(db, nil) + s.SetConfigProvider(&stubConfigProvider{settings: []MigratableSetting{ + operationalSetting(key, "https://api.tmi.dev/oauth2/callback", "environment", true), + }}) + + got, exists, err := s.GetResolvedString(context.Background(), key) + require.NoError(t, err) + require.True(t, exists) + require.Equal(t, "https://api.tmi.dev/oauth2/callback", got, + "an empty DB row must not shadow a configured value") +} + +// Bootstrap keys never consult the database, even when a row exists. +func TestGetResolvedString_BootstrapNeverReadsDatabase(t *testing.T) { + const key = "database.url" + db := setupSettingsTestDB(t) + seedRow(t, db, key, "postgres://wrong/db", models.SystemSettingOriginExplicit) + + s := NewSettingsService(db, nil) + s.SetConfigProvider(&stubConfigProvider{settings: []MigratableSetting{{ + Key: key, + Value: "postgres://localhost/tmi", + Type: "string", + Source: "config", + Explicit: false, + Class: config.ConfigClass{Category: config.CategoryBootstrap}, + }}}) + + got, exists, err := s.GetResolvedString(context.Background(), key) + require.NoError(t, err) + require.True(t, exists) + require.Equal(t, "postgres://localhost/tmi", got, + "a bootstrap key must resolve from config even when a DB row exists") +} + +// A key no layer supplies resolves to "not found" rather than an empty win. +func TestGetResolvedString_UnknownKey(t *testing.T) { + db := setupSettingsTestDB(t) + s := NewSettingsService(db, nil) + s.SetConfigProvider(&stubConfigProvider{settings: []MigratableSetting{}}) + + got, exists, err := s.GetResolvedString(context.Background(), "does.not.exist") + require.NoError(t, err) + require.False(t, exists) + require.Equal(t, "", got) +} diff --git a/api/settings_service.go b/api/settings_service.go index d6a2d212..2dfd430f 100644 --- a/api/settings_service.go +++ b/api/settings_service.go @@ -118,10 +118,16 @@ func (s *SettingsService) isProductionMode() bool { return os.Getenv("TMI_BUILD_MODE") == "production" } -// getConfigSetting retrieves a setting from the config provider if available. -// Returns the setting value and true if found, empty string and false otherwise. -// SEM@10b74985ed52c143cb0fb6e853b2d5f106de198f: fetch a setting from the config-provider cache, building the cache lazily (pure) -func (s *SettingsService) getConfigSetting(key string) (MigratableSetting, bool) { +// getConfigSettingRaw retrieves a setting from the config provider's cache +// without applying any precedence rule, building the cache lazily. The caller +// decides what to do with Class.Category and Explicit. +// +// getConfigSetting swallows non-explicit operational settings, which is right +// for its callers but wrong for GetResolvedString: the converged precedence +// rule needs to know both what the config layer holds AND whether an operator +// supplied it, and those are two different questions (#794). +// SEM@2daf3be663df9da54323f16d115f12d78d435c3f: fetch a config-layer setting with no precedence filtering, building the cache lazily (pure) +func (s *SettingsService) getConfigSettingRaw(key string) (MigratableSetting, bool) { if s.configProvider == nil { return MigratableSetting{}, false } @@ -151,6 +157,18 @@ func (s *SettingsService) getConfigSetting(key string) (MigratableSetting, bool) if !found { return MigratableSetting{}, false } + return setting, true +} + +// getConfigSetting retrieves a setting from the config provider, applying the +// #415 rule that a bare struct default must not shadow the database. +// Returns the setting and true when the config layer is authoritative. +// SEM@2daf3be663df9da54323f16d115f12d78d435c3f: fetch a config-layer setting, yielding to the database for bare defaults (pure) +func (s *SettingsService) getConfigSetting(key string) (MigratableSetting, bool) { + setting, found := s.getConfigSettingRaw(key) + if !found { + return MigratableSetting{}, false + } // Bootstrap settings (database URL, JWT secret, listen port) are never // database-backed, so the config layer is their only source. @@ -241,27 +259,59 @@ func (s *SettingsService) GetString(ctx context.Context, key string) (string, er return string(setting.Value), nil } -// GetDatabaseString retrieves a string setting value from the database only, -// bypassing the env/config-file priority that GetString applies. exists -// reports whether the row is present, so callers can distinguish a missing -// row (fall back to YAML) from an empty value. +// GetResolvedString retrieves a string setting by applying the single +// precedence rule that governs every scalar operational setting (#794). +// exists reports whether any layer supplied a usable value, so callers can +// distinguish "nothing configured anywhere" from an empty value. +// +// This replaced GetDatabaseString, which read the database only. Two +// accessors used to implement two deliberate, opposite rules — GetString +// (#415: an explicit config value wins, a bare struct default yields to the +// database) and GetDatabaseString (#767: the database always wins, config is +// a first-run fallback). Each was right about half the problem, and the half +// each got wrong caused a production outage on 2026-08-20: a registry-seeded +// default row nobody had ever set outranked a correctly-set +// TMI_OAUTH_CALLBACK_URL, so every OAuth login redirected to localhost. +// +// The missing distinction was on the database side. #415 already knew an +// operator-supplied config value from a struct default (MigratableSetting +// .Explicit); the database had no equivalent until SystemSetting.Origin, so a +// seeded row and an admin-set row were indistinguishable. With both halves +// present, one rule covers every case: +// +// config explicit? db explicit? winner +// no no database (both defaults; prefer the hot-reloadable one) +// no yes database (#415 preserved) +// yes no config (a seeded default never beats an operator) +// yes yes database (#419/#767 runtime editability preserved) +// +// Bootstrap settings are exempt: they are never database-backed, so the +// config layer is their only source. // -// This is the read used by the #419 runtime-config path: operational -// settings changed at runtime via the admin API must take effect without a -// redeploy, so for those keys the database wins over the YAML config and -// the YAML acts only as a first-run fallback (the reverse of GetString's -// precedence — see #767). // The read is retried on transient faults and the returned error is // classified (dberrors sentinels): callers on the #419 runtime path sit on // request hot paths (e.g. /oauth2/authorize) where an unretried ADB blip // (ORA-02396/03113/12537 class) would otherwise surface as a hard failure // PG testing cannot reproduce (oracle-db-admin review of #767). -// SEM@e733aa34cec28d44982ce9dd8937cf17ef810590: fetch a string setting from the database only, reporting row presence (reads DB) -func (s *SettingsService) GetDatabaseString(ctx context.Context, key string) (string, bool, error) { - var setting *models.SystemSetting +// SEM@2daf3be663df9da54323f16d115f12d78d435c3f: resolve a string setting across config and database by the converged precedence rule (reads DB) +func (s *SettingsService) GetResolvedString(ctx context.Context, key string) (string, bool, error) { + cfg, cfgFound := s.getConfigSettingRaw(key) + + // Bootstrap keys never reach the database at all. Keyed on the + // classification registry rather than on cfg.Class, so the guard still + // holds when the config provider is unset or does not emit the key — + // otherwise a bootstrap key would fall through to Get, whose "is a + // bootstrap key" error matches no dberrors pattern and would surface to + // the adapters as a non-transient fault (oracle-db-admin review, note 5). + // This is the same source Get itself consults. + if config.ClassificationCategoryFor(key) == config.CategoryBootstrap { + return cfg.Value, cfg.Value != "", nil + } + + var row *models.SystemSetting err := db.WithRetryableGormRead(ctx, db.DefaultRetryConfig(), func() error { var readErr error - setting, readErr = s.Get(ctx, key) + row, readErr = s.Get(ctx, key) if readErr != nil { return dberrors.Classify(readErr) } @@ -270,10 +320,32 @@ func (s *SettingsService) GetDatabaseString(ctx context.Context, key string) (st if err != nil { return "", false, err } - if setting == nil { - return "", false, nil + + // An empty row value is treated as absent. On Oracle '' binds as NULL, so + // an empty-but-present row is indistinguishable from a missing one anyway + // — collapsing them here keeps the rule engine-independent + // (oracle-db-admin review of #767, note 3). + dbHasValue := row != nil && string(row.Value) != "" + + switch { + case !dbHasValue: + // Nothing usable in the database — the config layer is all there is, + // explicit or not. + return cfg.Value, cfgFound && cfg.Value != "", nil + case row.IsExplicit(): + // An operator deliberately set this row, via the admin API or a + // dbtool import. It outranks config either way, which is what keeps + // runtime edits effective without a redeploy (#419/#767). + return string(row.Value), true, nil + case cfgFound && cfg.Explicit: + // The row is a seeded registry default and the operator supplied a + // value explicitly. This is the case that broke production. + return cfg.Value, cfg.Value != "", nil + default: + // Both sides are defaults; prefer the one an admin can change at + // runtime. + return string(row.Value), true, nil } - return string(setting.Value), true, nil } // GetInt retrieves an integer setting value. @@ -413,7 +485,7 @@ func (s *SettingsService) ListByPrefix(ctx context.Context, prefix string) ([]mo } // Set creates or updates a setting -// SEM@a3e7b116b059fa4c734b5c77def4c2de21df4dbc: store or update a system setting with type validation, encryption, and cache invalidation (reads DB) +// SEM@2daf3be663df9da54323f16d115f12d78d435c3f: store or update a system setting, stamping explicit origin, with type validation, encryption, and cache invalidation (reads DB) func (s *SettingsService) Set(ctx context.Context, setting *models.SystemSetting) error { logger := slogging.Get() @@ -453,6 +525,10 @@ func (s *SettingsService) Set(ctx context.Context, setting *models.SystemSetting // Encrypt value before saving to database dbSetting := *setting + // Set is always an operator-initiated write (admin API, dbtool, or any + // other caller) — stamp it explicit regardless of what the caller passed + // in, so an operator overwriting a seeded row promotes it (#794). + dbSetting.Origin = models.NullableDBVarchar{String: models.SystemSettingOriginExplicit, Valid: true} if encryptionEnabled { encrypted, err := s.encryptor.Encrypt(string(setting.Value)) if err != nil { @@ -468,8 +544,9 @@ func (s *SettingsService) Set(ctx context.Context, setting *models.SystemSetting return fmt.Errorf("failed to save setting %s: %w", setting.SettingKey, result.Error) } - // Update the caller's ModifiedAt to match what was saved + // Update the caller's ModifiedAt and Origin to match what was saved setting.ModifiedAt = dbSetting.ModifiedAt + setting.Origin = dbSetting.Origin // Invalidate cache s.invalidateCache(ctx, string(setting.SettingKey)) @@ -496,7 +573,7 @@ func (s *SettingsService) Delete(ctx context.Context, key string) error { } // SeedDefaults seeds the default settings if they don't exist -// SEM@ab3b70da0bebdbb7db5fefc97e15036dc3f5c4c5: insert default system settings into the database if they do not already exist (reads DB) +// SEM@2daf3be663df9da54323f16d115f12d78d435c3f: insert default system settings, stamped seeded origin, if they do not already exist (reads DB) func (s *SettingsService) SeedDefaults(ctx context.Context) error { logger := slogging.Get() defaults := models.DefaultSystemSettings() @@ -545,6 +622,11 @@ func (s *SettingsService) SeedDefaults(ctx context.Context) error { // Encrypt value before seeding dbSetting := setting + // Stamp seeded origin on every row this loop inserts — both the + // explicit models.DefaultSystemSettings() entries and the + // config-derived operational defaults appended above feed into + // `defaults`, so this single assignment covers both sources (#794). + dbSetting.Origin = models.NullableDBVarchar{String: models.SystemSettingOriginSeeded, Valid: true} if s.encryptor != nil && s.encryptor.IsEnabled() { encrypted, err := s.encryptor.Encrypt(string(setting.Value)) if err != nil { @@ -629,7 +711,7 @@ func (s *SettingsService) ReEncryptAll(ctx context.Context, modifiedBy *string) } // validateValue validates that the value matches the declared type -// SEM@cf52eebf6620ebf59d6d9c90dfb1c4f874f70341: validate that a setting value matches its declared type (pure) +// SEM@1a4ca5f99be4a25df66b2836e9b9f4c87628184a: validate that a setting value matches its declared type (pure) func (s *SettingsService) validateValue(setting *models.SystemSetting) error { switch setting.SettingType { case models.SystemSettingTypeInt: diff --git a/api/settings_service_test.go b/api/settings_service_test.go index dc766fd1..672fa778 100644 --- a/api/settings_service_test.go +++ b/api/settings_service_test.go @@ -9,8 +9,26 @@ import ( "github.com/ericfitz/tmi/internal/crypto" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "gorm.io/driver/sqlite" + "gorm.io/gorm" + gormlogger "gorm.io/gorm/logger" ) +// setupSettingsTestDB creates an in-memory SQLite DB with just the +// system_settings table, for tests that need a real SettingsService write +// path (SeedDefaults, Set, ReEncryptAll) rather than the nil-gormDB gate +// tests above. +func setupSettingsTestDB(t *testing.T) *gorm.DB { + t.Helper() + gormDB, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{ + Logger: gormlogger.Discard, + DisableForeignKeyConstraintWhenMigrating: true, + }) + require.NoError(t, err) + require.NoError(t, gormDB.AutoMigrate(&models.SystemSetting{})) + return gormDB +} + func TestSettingsService_MemCache(t *testing.T) { // Create a service with only memory cache (no Redis, no GORM) service := &SettingsService{ @@ -610,3 +628,129 @@ func TestSettingsService_SetAllowsPlaintextSecretOutsideProduction(t *testing.T) SettingType: models.SystemSettingTypeString, }) } + +// TestSettingsService_SeedDefaults_StampsSeededOrigin pins #794: every row +// SeedDefaults inserts — both the explicit models.DefaultSystemSettings() +// entries and the config-derived operational defaults it appends — must be +// stamped SystemSettingOriginSeeded, since no operator has set them. +func TestSettingsService_SeedDefaults_StampsSeededOrigin(t *testing.T) { + gormDB := setupSettingsTestDB(t) + svc := NewSettingsService(gormDB, nil) + + require.NoError(t, svc.SeedDefaults(context.Background())) + + var settings []models.SystemSetting + require.NoError(t, gormDB.Find(&settings).Error) + require.Greater(t, len(settings), len(models.DefaultSystemSettings()), + "expected config-derived operational defaults to be seeded alongside the explicit defaults") + + for _, s := range settings { + assert.Truef(t, s.Origin.Valid && s.Origin.String == models.SystemSettingOriginSeeded, + "setting %s: expected origin %q, got valid=%v value=%q", + s.SettingKey, models.SystemSettingOriginSeeded, s.Origin.Valid, s.Origin.String) + assert.Falsef(t, s.IsExplicit(), "setting %s: seeded row must not be IsExplicit()", s.SettingKey) + } + + // Spot-check one of the explicit models.DefaultSystemSettings() entries + // specifically, since that's the other of the two source lists. + var explicit models.SystemSetting + require.NoError(t, gormDB.Where("setting_key = ?", "rate_limit.requests_per_minute").First(&explicit).Error) + assert.Equal(t, models.SystemSettingOriginSeeded, explicit.Origin.String) +} + +// TestSettingsService_Set_StampsExplicitOrigin pins #794: Set always means an +// operator deliberately wrote this value, so it must stamp explicit origin +// regardless of what the caller's struct carried in, and it must reflect that +// back onto the caller's setting the same way it already does for ModifiedAt. +func TestSettingsService_Set_StampsExplicitOrigin(t *testing.T) { + gormDB := setupSettingsTestDB(t) + svc := NewSettingsService(gormDB, nil) + + setting := &models.SystemSetting{ + SettingKey: "ui.default_theme", + Value: "dark", + SettingType: models.SystemSettingTypeString, + } + require.NoError(t, svc.Set(context.Background(), setting)) + + assert.True(t, setting.Origin.Valid && setting.Origin.String == models.SystemSettingOriginExplicit, + "Set should reflect explicit origin back onto the caller's setting") + + var stored models.SystemSetting + require.NoError(t, gormDB.Where("setting_key = ?", "ui.default_theme").First(&stored).Error) + assert.Equal(t, models.SystemSettingOriginExplicit, stored.Origin.String) + assert.True(t, stored.IsExplicit()) + + t.Run("Set promotes an already-seeded row to explicit", func(t *testing.T) { + seeded := models.SystemSetting{ + SettingKey: "session.timeout_minutes", + Value: "60", + SettingType: models.SystemSettingTypeInt, + Origin: models.NullableDBVarchar{String: models.SystemSettingOriginSeeded, Valid: true}, + } + require.NoError(t, gormDB.Create(&seeded).Error) + + update := &models.SystemSetting{ + SettingKey: "session.timeout_minutes", + Value: "90", + SettingType: models.SystemSettingTypeInt, + } + require.NoError(t, svc.Set(context.Background(), update)) + + var stored models.SystemSetting + require.NoError(t, gormDB.Where("setting_key = ?", "session.timeout_minutes").First(&stored).Error) + assert.Equal(t, models.SystemSettingOriginExplicit, stored.Origin.String, + "operator overwriting a seeded row via Set must promote it to explicit") + }) +} + +// TestSettingsService_ReEncryptAll_PreservesOrigin is the #794 regression +// guard: ReEncryptAll loads each row and re-Saves it via a raw gorm.DB.Save +// (not SettingsService.Set), so it must NOT touch Origin. If key rotation +// promoted every seeded row to explicit, the whole precedence feature would +// silently break — every operational key would suddenly out-rank config. +func TestSettingsService_ReEncryptAll_PreservesOrigin(t *testing.T) { + gormDB := setupSettingsTestDB(t) + + key := make([]byte, 32) + for i := range key { + key[i] = byte(i) + } + enc, err := crypto.NewSettingsEncryptorFromKeys(key, nil, 1) + require.NoError(t, err) + + svc := NewSettingsService(gormDB, nil) + svc.SetEncryptor(enc) + + seeded := models.SystemSetting{ + SettingKey: "session.timeout_minutes", + Value: "60", // plaintext; ReEncryptAll must accept this as well as already-encrypted values + SettingType: models.SystemSettingTypeInt, + Origin: models.NullableDBVarchar{String: models.SystemSettingOriginSeeded, Valid: true}, + } + require.NoError(t, gormDB.Create(&seeded).Error) + + explicit := models.SystemSetting{ + SettingKey: "ui.default_theme", + Value: "dark", + SettingType: models.SystemSettingTypeString, + Origin: models.NullableDBVarchar{String: models.SystemSettingOriginExplicit, Valid: true}, + } + require.NoError(t, gormDB.Create(&explicit).Error) + + count, settingErrors, err := svc.ReEncryptAll(context.Background(), nil) + require.NoError(t, err) + assert.Empty(t, settingErrors) + assert.Equal(t, 2, count) + + var reloadedSeeded models.SystemSetting + require.NoError(t, gormDB.Where("setting_key = ?", "session.timeout_minutes").First(&reloadedSeeded).Error) + assert.Equal(t, models.SystemSettingOriginSeeded, reloadedSeeded.Origin.String, + "ReEncryptAll must not promote a seeded row to explicit") + assert.False(t, reloadedSeeded.IsExplicit()) + + var reloadedExplicit models.SystemSetting + require.NoError(t, gormDB.Where("setting_key = ?", "ui.default_theme").First(&reloadedExplicit).Error) + assert.Equal(t, models.SystemSettingOriginExplicit, reloadedExplicit.Origin.String) + assert.True(t, reloadedExplicit.IsExplicit()) +} diff --git a/api/version.go b/api/version.go index ad3b4f6d..121ebece 100644 --- a/api/version.go +++ b/api/version.go @@ -52,7 +52,7 @@ var ( // Minor version number VersionMinor = "8" // Patch version number - VersionPatch = "21" + VersionPatch = "22" // VersionPreRelease is the pre-release label (e.g., "rc.0", "beta.1"), empty for stable releases VersionPreRelease = "" // GitCommit is the git commit hash from build diff --git a/auth/config_adapter.go b/auth/config_adapter.go index 26112722..690efe9a 100644 --- a/auth/config_adapter.go +++ b/auth/config_adapter.go @@ -314,6 +314,19 @@ func migrateSchemaForConfigAdapter(gormDB *db.GormDB, allModels []any, desiredFP return fmt.Errorf("failed to ensure sparse-user email index: %w", err) } + // #794: stamp explicit origin on pre-existing system_settings rows that + // show operator intent, and enforce the origin CHECK constraint -- same + // placement and reasoning as cmd/server/main.go's runMigrationsLocked and + // cmd/dbtool/schema.go's runSchema. + if updated, err := dbschema.BackfillSystemSettingOrigin(gormDB.DB()); err != nil { + return fmt.Errorf("failed to backfill system_settings.origin: %w", err) + } else if updated > 0 { + logger.Info("[AUTH_CONFIG_ADAPTER] Backfilled explicit origin on %d pre-existing system_settings row(s)", updated) + } + if err := dbschema.EnsureSystemSettingOriginCheckConstraint(gormDB.DB()); err != nil { + return fmt.Errorf("failed to ensure system_settings.origin CHECK constraint: %w", err) + } + return nil } diff --git a/auth/handlers.go b/auth/handlers.go index ac28f3e6..00e4c965 100644 --- a/auth/handlers.go +++ b/auth/handlers.go @@ -188,6 +188,17 @@ func (h *Handlers) SetRuntimeConfigReader(r RuntimeConfigReader) { h.runtimeCfg = r } +// RuntimeConfigReader returns the wired reader, or nil if none was set. +// +// Exposed so components built outside the auth package — the JWT +// authenticator in cmd/server, which needs auth.everyone_is_a_reviewer — +// can resolve operational settings through the same reader rather than +// reading a boot-time config snapshot that ignores the database (#794). +// SEM@08e19a77d4d2c499f116e1a1ee3c875c06407335: return the registered runtime config reader, or nil when unset (pure) +func (h *Handlers) RuntimeConfigReader() RuntimeConfigReader { + return h.runtimeCfg +} + // clientCallbackAllowList returns the allowlist read from the runtime // config (DB-backed) when available, otherwise the YAML snapshot. // diff --git a/auth/runtime_config_reader.go b/auth/runtime_config_reader.go index 21bac4ee..0b184add 100644 --- a/auth/runtime_config_reader.go +++ b/auth/runtime_config_reader.go @@ -41,4 +41,17 @@ type RuntimeConfigReader interface { // GetOAuthCallbackURL returns the configured OAuth callback URL used // when redirecting back from an external provider. GetOAuthCallbackURL(ctx context.Context) string + + // IsEveryoneAReviewer reports whether every authenticated user should be + // auto-added to the Security Reviewers group. + // + // Before #794 this setting had no runtime reader at all: the only + // consumer read config.Auth.EveryoneIsAReviewer directly, so the + // database row existed, was displayed by the admin API, and did + // absolutely nothing. Editing it at runtime appeared to work and + // silently had no effect. + // + // Returns false on any read error (fail-closed): failing to grant a + // group membership is recoverable, granting one wrongly is not. + IsEveryoneAReviewer(ctx context.Context) bool } diff --git a/auth/runtime_config_reader_test.go b/auth/runtime_config_reader_test.go index bdb0fb37..dd0f6e40 100644 --- a/auth/runtime_config_reader_test.go +++ b/auth/runtime_config_reader_test.go @@ -12,6 +12,7 @@ type stubRuntimeReader struct { allowListErr error samlEnabled bool callbackURL string + everyoneReviews bool } func (s *stubRuntimeReader) GetClientCallbackAllowList(_ context.Context) ([]string, bool, error) { @@ -23,6 +24,9 @@ func (s *stubRuntimeReader) IsSAMLEnabled(_ context.Context) bool { func (s *stubRuntimeReader) GetOAuthCallbackURL(_ context.Context) string { return s.callbackURL } +func (s *stubRuntimeReader) IsEveryoneAReviewer(_ context.Context) bool { + return s.everyoneReviews +} func TestHandlers_clientCallbackAllowList_PrefersRuntimeReader(t *testing.T) { h := &Handlers{ diff --git a/cmd/dbtool/config.go b/cmd/dbtool/config.go index f2ebfc96..f6541757 100644 --- a/cmd/dbtool/config.go +++ b/cmd/dbtool/config.go @@ -30,7 +30,7 @@ import ( // preserves comments, key ordering, and value types verbatim — none of // which the *-migrated.yml writer below does. // -// SEM@e7880ae29f527fb2d814f6d7b7c13280082fa033: migrate operational settings from a config file into the database, optionally writing a bootstrap-only YAML (writes DB) +// SEM@0000000000000000000000000000000000000000: migrate operational settings from a config file into the database with explicit origin, optionally writing a bootstrap-only YAML (writes DB) func runConfigSeed(db *testdb.TestDB, inputFile, outputFile string, overwrite, dryRun, emitLegacyMigratedYAML bool) error { log := slogging.Get() @@ -139,12 +139,16 @@ func runConfigSeed(db *testdb.TestDB, inputFile, outputFile string, overwrite, d } description := s.Description + // origin is stamped explicit: an operator ran --import-config to put + // this value here deliberately (#794), including on the #792 + // snapshot/restore path where this now outranks env for these keys. setting := models.SystemSetting{ SettingKey: models.DBVarchar(s.Key), Value: models.DBText(value), SettingType: models.DBVarchar(s.Type), Description: models.NewNullableDBText(&description), ModifiedAt: time.Now(), + Origin: models.NullableDBVarchar{String: models.SystemSettingOriginExplicit, Valid: true}, } if exists { @@ -156,6 +160,7 @@ func runConfigSeed(db *testdb.TestDB, inputFile, outputFile string, overwrite, d "value": value, "setting_type": s.Type, "description": description, + "origin": models.SystemSettingOriginExplicit, }).Error; updateErr != nil { return fmt.Errorf("failed to update setting %s: %w", s.Key, updateErr) } diff --git a/cmd/dbtool/config_origin_test.go b/cmd/dbtool/config_origin_test.go new file mode 100644 index 00000000..6929e146 --- /dev/null +++ b/cmd/dbtool/config_origin_test.go @@ -0,0 +1,68 @@ +package main + +import ( + "os" + "path/filepath" + "testing" + + "github.com/ericfitz/tmi/api/models" +) + +// TestRunConfigSeed_StampsExplicitOrigin pins #794: dbtool --import-config +// (runConfigSeed) marks every row it writes explicit, on both the create and +// the update path, since an operator running the import deliberately put +// these values in the database. +func TestRunConfigSeed_StampsExplicitOrigin(t *testing.T) { + db, _ := newExportTestDB(t) + + dir := t.TempDir() + inputPath := filepath.Join(dir, "settings.yml") + yamlContent := "operator:\n name: \"Eric\"\n" + if err := os.WriteFile(inputPath, []byte(yamlContent), 0o600); err != nil { + t.Fatal(err) + } + + // Create path. + if err := runConfigSeed(db, inputPath, "", false, false, false); err != nil { + t.Fatalf("runConfigSeed (create): %v", err) + } + + var created models.SystemSetting + if err := db.DB().Where("setting_key = ?", "operator.name").First(&created).Error; err != nil { + t.Fatalf("load created setting: %v", err) + } + if !created.Origin.Valid || created.Origin.String != models.SystemSettingOriginExplicit { + t.Errorf("created row: Origin = (valid=%v, %q), want %q", created.Origin.Valid, created.Origin.String, models.SystemSettingOriginExplicit) + } + if !created.IsExplicit() { + t.Error("created row: IsExplicit() = false, want true") + } + + // Update path: re-run with --overwrite against a changed value, and + // against a row whose origin was seeded, to prove the update path + // promotes it too. + if err := db.DB().Model(&created).Updates(map[string]any{ + "origin": models.SystemSettingOriginSeeded, + }).Error; err != nil { + t.Fatalf("reset origin to seeded for update-path test: %v", err) + } + + yamlContent = "operator:\n name: \"Someone Else\"\n" + if err := os.WriteFile(inputPath, []byte(yamlContent), 0o600); err != nil { + t.Fatal(err) + } + if err := runConfigSeed(db, inputPath, "", true, false, false); err != nil { + t.Fatalf("runConfigSeed (update): %v", err) + } + + var updated models.SystemSetting + if err := db.DB().Where("setting_key = ?", "operator.name").First(&updated).Error; err != nil { + t.Fatalf("load updated setting: %v", err) + } + if !updated.Origin.Valid || updated.Origin.String != models.SystemSettingOriginExplicit { + t.Errorf("updated row: Origin = (valid=%v, %q), want %q", updated.Origin.Valid, updated.Origin.String, models.SystemSettingOriginExplicit) + } + if string(updated.Value) != "Someone Else" { + t.Errorf("updated row: Value = %q, want %q (sanity check the update actually ran)", updated.Value, "Someone Else") + } +} diff --git a/cmd/dbtool/data_db.go b/cmd/dbtool/data_db.go index 2b0ba934..33c79c99 100644 --- a/cmd/dbtool/data_db.go +++ b/cmd/dbtool/data_db.go @@ -179,7 +179,7 @@ func setQuotas(db *testdb.TestDB, userInternalUUID string, quota map[string]any) return nil } -// SEM@926d7b92ff82030ff3a1c03b2307247302613d38: upsert a system setting key-value pair, skipping empty values (reads DB) +// SEM@0000000000000000000000000000000000000000: upsert a system setting key-value pair with explicit origin, skipping empty values (reads DB) func seedSetting(db *testdb.TestDB, entry SeedEntry) (*SeedResult, error) { log := slogging.Get() @@ -205,11 +205,14 @@ func seedSetting(db *testdb.TestDB, entry SeedEntry) (*SeedResult, error) { description, _ := entry.Data["description"].(string) + // origin is stamped explicit: a hand-authored seed entry is an operator + // deliberately setting this value, the same as --import-config (#794). setting := models.SystemSetting{ SettingKey: models.DBVarchar(key), Value: models.DBText(value), SettingType: models.DBVarchar(settingType), Description: models.NewNullableDBText(&description), + Origin: models.NullableDBVarchar{String: models.SystemSettingOriginExplicit, Valid: true}, } var existing models.SystemSetting @@ -218,6 +221,7 @@ func seedSetting(db *testdb.TestDB, entry SeedEntry) (*SeedResult, error) { "value": value, "setting_type": settingType, "description": description, + "origin": models.SystemSettingOriginExplicit, }).Error; err != nil { return nil, fmt.Errorf("failed to update setting %s: %w", key, err) } diff --git a/cmd/dbtool/schema.go b/cmd/dbtool/schema.go index 686f7f98..0af0eca3 100644 --- a/cmd/dbtool/schema.go +++ b/cmd/dbtool/schema.go @@ -130,6 +130,25 @@ func runSchemaLocked(db *testdb.TestDB) error { return fmt.Errorf("failed to ensure sparse-user email index: %w", err) } + // #794: stamp explicit origin on pre-existing system_settings rows that + // show operator intent (modified_by set, or a value that no longer + // matches the registry default), same placement and reasoning as + // cmd/server/main.go's runMigrationsLocked. dbtool is the + // admin-privileged remediation path, so a failure here is surfaced + // rather than swallowed. + if updated, err := dbschema.BackfillSystemSettingOrigin(db.DB()); err != nil { + return fmt.Errorf("failed to backfill system_settings.origin: %w", err) + } else if updated > 0 { + log.Info("Backfilled explicit origin on %d pre-existing system_settings row(s)", updated) + } + + // #794: enforce system_settings.origin IN (NULL, 'seeded', 'explicit') at + // the database level. Same placement and reasoning as + // cmd/server/main.go's runMigrationsLocked. + if err := dbschema.EnsureSystemSettingOriginCheckConstraint(db.DB()); err != nil { + return fmt.Errorf("failed to ensure system_settings.origin CHECK constraint: %w", err) + } + // Step 2: Seed system data log.Info("Seeding system data (groups, webhook deny list)...") if err := seed.SeedDatabase(db.DB()); err != nil { diff --git a/cmd/server/jwt_auth.go b/cmd/server/jwt_auth.go index 96e0d13c..b0c4a5ec 100644 --- a/cmd/server/jwt_auth.go +++ b/cmd/server/jwt_auth.go @@ -5,6 +5,7 @@ import ( "fmt" "net/http" "strings" + "sync" "time" "github.com/ericfitz/tmi/api" @@ -427,7 +428,7 @@ func (v *TicketValidator) ValidateTicket(c *gin.Context, ticketStr string) error } // JWTAuthenticator orchestrates the JWT authentication process -// SEM@834ed0d09c836060ae9619f32b156a5d710fd22e: orchestrator that coordinates token extraction, validation, blacklist check, and claims population (mutates shared state) +// SEM@2daf3be663df9da54323f16d115f12d78d435c3f: orchestrator that coordinates token extraction, validation, blacklist check, and claims population (mutates shared state) type JWTAuthenticator struct { config *config.Config tokenExtractor *TokenExtractor @@ -435,10 +436,70 @@ type JWTAuthenticator struct { blacklistChecker *TokenBlacklistChecker claimsExtractor *ClaimsExtractor ticketValidator *TicketValidator + // authHandlers is retained so the authenticator can reach the runtime + // config reader wired onto it after construction. It is read lazily + // rather than captured, because SetRuntimeConfigReader runs later in + // startup than the JWTMiddleware factory that builds this struct. + authHandlers *auth.Handlers + + // Memoized auth.everyone_is_a_reviewer — see everyoneIsAReviewer for why + // this one setting is cached here rather than read per request. + reviewerMu sync.RWMutex + reviewerCached bool + reviewerExpiry time.Time +} + +// everyoneIsAReviewerTTL bounds how stale the memoized value may be. It +// matches SettingsCacheTTL, so a runtime edit takes effect on the same +// timescale as every other hot operational setting. +const everyoneIsAReviewerTTL = 60 * time.Second + +// everyoneIsAReviewer reports whether every authenticated user should be +// auto-added to the Security Reviewers group, preferring the runtime resolver +// over the boot-time config snapshot. +// +// Before #794 the caller read a.config.Auth.EveryoneIsAReviewer directly, so +// the system_settings row for this key was pure drift: visible in the admin +// API, editable, and inert. Falls back to the config snapshot when no reader +// is wired, which is what unit tests and any pre-#419 path rely on. +// +// The result is memoized because this sits at the tail of +// AuthenticateRequest — on EVERY authenticated request, not just /oauth2/*. +// The settings service picks its cache tier once at construction and never +// falls back, so with Redis unreachable an uncached read here would put one +// SELECT on a session-limited ADB per authenticated request, and during an +// ADB blip each would first pay the full WithRetryableGormRead budget on the +// global auth path. Memoizing turns that back into at most one read per TTL +// (oracle-db-admin review, note 3). +// SEM@2daf3be663df9da54323f16d115f12d78d435c3f: report whether all users are auto-added as security reviewers, memoized (reads DB) +func (a *JWTAuthenticator) everyoneIsAReviewer(ctx context.Context) bool { + if a.authHandlers == nil { + return a.config.Auth.EveryoneIsAReviewer + } + r := a.authHandlers.RuntimeConfigReader() + if r == nil { + return a.config.Auth.EveryoneIsAReviewer + } + + a.reviewerMu.RLock() + cached, expiry := a.reviewerCached, a.reviewerExpiry + a.reviewerMu.RUnlock() + if time.Now().Before(expiry) { + return cached + } + + v := r.IsEveryoneAReviewer(ctx) + + a.reviewerMu.Lock() + a.reviewerCached = v + a.reviewerExpiry = time.Now().Add(everyoneIsAReviewerTTL) + a.reviewerMu.Unlock() + + return v } // NewJWTAuthenticator creates a new JWT authenticator -// SEM@834ed0d09c836060ae9619f32b156a5d710fd22e: build a JWTAuthenticator wiring all sub-components together (pure) +// SEM@2daf3be663df9da54323f16d115f12d78d435c3f: build a JWTAuthenticator wiring all sub-components together (pure) func NewJWTAuthenticator(cfg *config.Config, tokenBlacklist *auth.TokenBlacklist, authHandlers *auth.Handlers, ticketValidator *TicketValidator) *JWTAuthenticator { return &JWTAuthenticator{ config: cfg, @@ -447,11 +508,12 @@ func NewJWTAuthenticator(cfg *config.Config, tokenBlacklist *auth.TokenBlacklist blacklistChecker: NewTokenBlacklistChecker(tokenBlacklist), claimsExtractor: NewClaimsExtractor(authHandlers, cfg), ticketValidator: ticketValidator, + authHandlers: authHandlers, } } // AuthenticateRequest performs the complete JWT authentication process -// SEM@834ed0d09c836060ae9619f32b156a5d710fd22e: authenticate a request end-to-end: extract, validate, blacklist-check, set claims, and auto-promote user (reads DB) +// SEM@2daf3be663df9da54323f16d115f12d78d435c3f: authenticate a request end-to-end: extract, validate, blacklist-check, set claims, and auto-promote user (reads DB) func (a *JWTAuthenticator) AuthenticateRequest(c *gin.Context) error { logger := slogging.GetContextLogger(c) @@ -540,7 +602,7 @@ func (a *JWTAuthenticator) AuthenticateRequest(c *gin.Context) error { } // Auto-promotion: If everyone_is_a_reviewer is enabled, add user to Security Reviewers - if a.config.Auth.EveryoneIsAReviewer { + if a.everyoneIsAReviewer(c.Request.Context()) { if err := a.autoPromoteUserToReviewer(c, logger); err != nil { logger.Warn("Auto-promotion to reviewer failed (non-fatal): %v", err) } @@ -672,7 +734,7 @@ func extractAuthTime(claims jwt.MapClaims) *time.Time { } // AuthError represents an authentication error -// SEM@4544fe064a8e91cf6d4c8a495f43f7f7830f6fe7: structured auth error carrying an OAuth error code and HTTP status code (pure) +// SEM@7383e0ea99036c9a251ff7eefa5cb784ea3829a8: structured auth error carrying an OAuth error code and HTTP status code (pure) type AuthError struct { Code string Description string diff --git a/cmd/server/main.go b/cmd/server/main.go index 0bec867d..057ab7d8 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -566,6 +566,28 @@ func migrateSchema(ctx context.Context, gormDB *db.GormDB, dbType string) error logger.Info("Migrated %d severity values from 'none' to 'informational'", result.RowsAffected) } + // #794: stamp explicit origin on pre-existing system_settings rows that + // show operator intent (modified_by set, or a value that no longer + // matches the registry default), so they keep the authority they had + // before the origin column existed. Idempotent — see + // internal/dbschema/system_setting_origin_backfill.go. Non-fatal: a + // database issue here just leaves the affected rows reading as seeded + // (the fail-safe default) until the next successful boot retries. + if updated, err := dbschema.BackfillSystemSettingOrigin(gormDB.DB()); err != nil { + logger.Warn("BackfillSystemSettingOrigin failed (non-fatal; affected rows keep reading as seeded until the next successful boot): %v", err) + } else if updated > 0 { + logger.Info("Backfilled explicit origin on %d pre-existing system_settings row(s)", updated) + } + + // #794: enforce system_settings.origin IN (NULL, 'seeded', 'explicit') at + // the database level, turning the "never write '' here" model comment + // into an actual invariant. Idempotent; non-fatal — a missing + // defense-in-depth constraint is a smaller outage than a server that + // will not boot, and the next boot retries. + if err := dbschema.EnsureSystemSettingOriginCheckConstraint(gormDB.DB()); err != nil { + logger.Warn("EnsureSystemSettingOriginCheckConstraint failed (non-fatal; the origin invariant is not enforced at the database level): %v", err) + } + // Seed required data (everyone group, webhook deny list) if err := seed.SeedDatabase(gormDB.DB()); err != nil { return fmt.Errorf("failed to seed database: %w", err) @@ -867,6 +889,10 @@ func setupRouter(config *config.Config) (*gin.Engine, *api.Server, *api.Embeddin logger.Info("Default system settings seeded successfully") } + // Warn if an explicit config/environment value disagrees with the database + // (see #794 — this is what would have caught the 2026-08-20 outage). + warnIfConfigDatabaseDiverges(context.Background(), settingsService, config, logger) + // Set config provider for settings migration endpoint and priority lookups // Configuration priority: environment > config file > database // The config provider (environment/config file values) takes precedence over database values @@ -961,6 +987,9 @@ func setupRouter(config *config.Config) (*gin.Engine, *api.Server, *api.Embeddin // operational config (client_callback_allowlist, saml_enabled, // oauth_callback_url) come from system_settings rather than the // YAML snapshot loaded at boot. #419. + // The JWT authenticator reads the same reader back off authHandlers for + // auth.everyone_is_a_reviewer, which until #794 was read straight off + // the boot-time config struct and so ignored its database row entirely. authHandlers.SetRuntimeConfigReader(api.NewRuntimeConfigReaderAdapter(settingsService)) logger.Info("Runtime config reader wired into auth handlers (#419)") } else { diff --git a/cmd/server/startup_checks.go b/cmd/server/startup_checks.go index eeaa18f2..352d71e7 100644 --- a/cmd/server/startup_checks.go +++ b/cmd/server/startup_checks.go @@ -2,6 +2,7 @@ package main import ( "context" + "fmt" "strings" "github.com/ericfitz/tmi/api/models" @@ -10,13 +11,25 @@ import ( "github.com/ericfitz/tmi/internal/slogging" ) -// secretKeyGetter is the subset of SettingsServiceInterface needed by the startup check. -// Using a minimal interface makes warnIfPlaintextSecretsAtRest unit-testable without a real DB. +// secretKeyGetter is the subset of SettingsServiceInterface needed by the startup checks +// in this file. Using a minimal interface makes them unit-testable without a real DB. // SEM@99c7bf92a70c0288330ba2861b823dbda8ce3aa2: fetch a single system setting by key (reads DB) type secretKeyGetter interface { Get(ctx context.Context, key string) (*models.SystemSetting, error) } +// settingsLister is the subset of SettingsServiceInterface needed by the +// divergence check. It reads every row in one query rather than one query per +// key: GetMigratableSettings emits 131 keys and the explicit subset of a +// container deployment is realistically 30-80, which as serial uncached round +// trips costs seconds of startup on Oracle ADB — materially more on a cold +// Always Free instance that has just auto-started, where a slow boot can trip +// a readiness deadline into a restart loop (oracle-db-admin review, note 4). +// SEM@2daf3be663df9da54323f16d115f12d78d435c3f: list every stored system setting in one read (reads DB) +type settingsLister interface { + List(ctx context.Context) ([]models.SystemSetting, error) +} + // warnIfPlaintextSecretsAtRest checks whether any Secret-classified settings have a // non-empty plaintext value stored in the database while encryption is disabled. // @@ -77,14 +90,123 @@ func warnIfPlaintextSecretsAtRest( } } +// warnIfConfigDatabaseDiverges checks whether any operational setting has both an +// explicit config/environment value and a database row whose value disagrees with it. +// +// This is the check that would have caught the 2026-08-20 outage: an operator had +// TMI_OAUTH_CALLBACK_URL set correctly the whole time, but a seeded default row in +// the database silently outranked it, so OAuth redirected to localhost and every +// provider rejected the login. A second regression in the same deploy left SAML +// silently disabled for the same reason. Nothing warned. This does. +// +// A config value only counts if MigratableSetting.Explicit is true (an operator +// actually set it, not just the struct default), because a default disagreeing with +// the database is normal and not worth flagging. Bootstrap-category settings are +// skipped entirely — they are never database-backed. For each remaining divergence, +// the database row's own Origin tells us which value actually wins: an explicit DB +// row (SystemSetting.IsExplicit()) outranks the config value; a merely-seeded row +// does not, so the config value wins there — see the precedence table on #794. +// +// No setting's value is ever logged, secret or not. Making log safety depend on +// per-key classification metadata was one mistake away from a credential leak, +// and the mistake was already present — see the comment at the divergence +// append below. +// +// The function NEVER returns an error; a warning is informational only and must not +// abort startup. Call this after both the settings service and config are ready — +// ideally after SeedDefaults, so DB rows actually exist to compare against. +// SEM@2daf3be663df9da54323f16d115f12d78d435c3f: warn when an explicit config value and an explicit DB row disagree for an operational setting (reads DB) +func warnIfConfigDatabaseDiverges( + ctx context.Context, + svc settingsLister, + cfg *config.Config, + logger slogging.SimpleLogger, +) { + settings := cfg.GetMigratableSettings() + + // One query for every row, rather than one per explicitly-configured key. + // Best-effort: if it fails there is nothing to compare against and the + // check simply does not run — it must never abort startup. + rows, err := svc.List(ctx) + if err != nil { + logger.Debug("startup check: could not list settings for divergence check: %v", err) + return + } + stored := make(map[string]*models.SystemSetting, len(rows)) + for i := range rows { + stored[string(rows[i].SettingKey)] = &rows[i] + } + + var divergences []string + for _, s := range settings { + if s.Class.Category != config.CategoryOperational { + continue // bootstrap keys are config-only; there is no DB row to diverge from + } + if !s.Explicit { + continue // struct default; disagreeing with the DB is normal, not a warning + } + + row, ok := stored[s.Key] + if !ok || string(row.Value) == "" { + continue // no DB row yet, or an empty value — nothing to compare + } + + dbValue := string(row.Value) + if dbValue == s.Value { + continue // config and database agree + } + + winner := "config (the database row is only a seeded default)" + if row.IsExplicit() { + winner = "database (also set explicitly)" + } + + // Only the key and the winner — never either value, for any key. + // + // An earlier revision printed values for keys that were not + // Secret-classified. That was one metadata mistake away from a + // credential leak, and the mistake was already present: the + // auth.oauth.providers. / auth.saml.providers. / content_oauth.providers. + // subtrees carry Class.Secret=false (a blanket true there would mis-mask + // non-secret sub-keys like .client_id), so a check keyed on + // classification printed real OAuth client secrets in plaintext. That is + // also what CodeQL's go/clear-text-logging objects to: not the specific + // keys, but making log safety depend on every key being classified + // correctly forever. + // + // The key name and the winner are what an operator actually needs — they + // say which setting to inspect and which layer is in force. The values + // are one `SELECT` and one `printenv` away, in a context where seeing + // them is a deliberate act rather than a side effect of booting. + divergences = append(divergences, fmt.Sprintf("%s (winner=%s)", s.Key, winner)) + } + + if len(divergences) == 0 { + return + } + + msg := fmt.Sprintf( + "CONFIG WARNING: %d operational setting(s) have an explicit config/environment "+ + "value that disagrees with the value stored in the database. Check whether an "+ + "admin deliberately edited the value at runtime (in which case the database is "+ + "correct and the config/env value is stale), or whether the database row is a "+ + "leftover seeded default from before this config/env value was introduced (in "+ + "which case it should be corrected or deleted). Values are deliberately not "+ + "logged; inspect them via GET /admin/settings/{key} and the process environment. "+ + "Affected: [%s]", + len(divergences), strings.Join(divergences, "; "), + ) + logger.Warn("%s", msg) +} + // secretClassifiedKeys returns the list of setting keys that are marked Secret in the // migratable settings registry derived from the current configuration. -// SEM@99c7bf92a70c0288330ba2861b823dbda8ce3aa2: list config keys classified as secret from the migratable settings registry (pure) +// SEM@2daf3be663df9da54323f16d115f12d78d435c3f: list config keys classified as secret from the migratable settings registry (pure) func secretClassifiedKeys(cfg *config.Config) []string { all := cfg.GetMigratableSettings() keys := make([]string, 0, len(all)) for _, s := range all { - if s.Secret { + if s.IsSecret() { keys = append(keys, s.Key) } } diff --git a/cmd/server/startup_checks_test.go b/cmd/server/startup_checks_test.go index f36876c2..8639e19f 100644 --- a/cmd/server/startup_checks_test.go +++ b/cmd/server/startup_checks_test.go @@ -3,6 +3,8 @@ package main import ( "context" "fmt" + "os" + "path/filepath" "strings" "sync" "testing" @@ -80,12 +82,17 @@ type mockSecretKeyGetter struct { store map[string]string // errKeys maps key → error to return for that key. errKeys map[string]error + // origins maps key → Origin value (models.SystemSettingOriginSeeded or + // models.SystemSettingOriginExplicit). Absent means NULL, which + // SystemSetting.IsExplicit() treats as NOT explicit (fail-safe). + origins map[string]string } func newMockSecretKeyGetter(values map[string]string) *mockSecretKeyGetter { return &mockSecretKeyGetter{ store: values, errKeys: make(map[string]error), + origins: make(map[string]string), } } @@ -97,9 +104,34 @@ func (m *mockSecretKeyGetter) Get(_ context.Context, key string) (*models.System if !ok { return nil, nil // not found } - return &models.SystemSetting{SettingKey: models.DBVarchar(key), Value: models.DBText(val)}, nil + setting := &models.SystemSetting{SettingKey: models.DBVarchar(key), Value: models.DBText(val)} + if origin, ok := m.origins[key]; ok { + setting.Origin = models.NullableDBVarchar{String: origin, Valid: true} + } + return setting, nil +} + +// listErr, when set, makes List fail — the divergence check must then skip +// silently rather than abort startup. +func (m *mockSecretKeyGetter) List(_ context.Context) ([]models.SystemSetting, error) { + if err, ok := m.errKeys[listErrSentinelKey]; ok { + return nil, err + } + out := make([]models.SystemSetting, 0, len(m.store)) + for key, val := range m.store { + s := models.SystemSetting{SettingKey: models.DBVarchar(key), Value: models.DBText(val)} + if origin, ok := m.origins[key]; ok { + s.Origin = models.NullableDBVarchar{String: origin, Valid: true} + } + out = append(out, s) + } + return out, nil } +// listErrSentinelKey is a reserved errKeys entry meaning "fail the whole List +// call" rather than one key, since List has no per-key error path. +const listErrSentinelKey = "\x00list" + // minimalConfigWithBuildMode builds the smallest *config.Config that produces // a known set of Secret-classified keys and the desired build mode. // We use auth.jwt.secret and database.redis.password as two well-known Secret keys. @@ -302,6 +334,229 @@ func TestWarnIfPlaintextSecretsAtRest_AggregatesMultipleKeys(t *testing.T) { } } +// --- warnIfConfigDatabaseDiverges tests --- + +// minimalOperationalConfig builds the smallest *config.Config needed to exercise +// warnIfConfigDatabaseDiverges. websocketTimeoutSeconds feeds the operational key +// "websocket.inactivity_timeout_seconds"; timmyLLMAPIKey feeds the operational, +// Secret-classified key "timmy.llm_api_key". Both are left at their struct +// defaults ("not explicit") unless the caller also sets the matching env var via +// t.Setenv, since Explicit is computed from Source=="environment" || file-key +// presence, not from the field being non-zero. +func minimalOperationalConfig(websocketTimeoutSeconds int, timmyLLMAPIKey string) *config.Config { + return &config.Config{ + Auth: config.AuthConfig{ + BuildMode: "dev", + JWT: config.JWTConfig{ + Secret: "supersecret", + ExpirationSeconds: 3600, + SigningMethod: "HS256", + }, + }, + Database: config.DatabaseConfig{ + URL: "postgres://localhost/tmi", + }, + Logging: config.LoggingConfig{ + Level: "info", + LogDir: "logs", + }, + Secrets: config.SecretsConfig{ + Provider: "env", + }, + WebSocket: config.WebSocketConfig{ + InactivityTimeoutSeconds: websocketTimeoutSeconds, + }, + Timmy: config.TimmyConfig{ + LLMAPIKey: timmyLLMAPIKey, + }, + } +} + +// TestWarnIfConfigDatabaseDiverges_ExplicitConfigDiffersFromDB verifies that an +// explicit config value disagreeing with the DB row produces exactly one WARN +// naming the affected key. +func TestWarnIfConfigDatabaseDiverges_ExplicitConfigDiffersFromDB(t *testing.T) { + t.Setenv("TMI_WEBSOCKET_INACTIVITY_TIMEOUT_SECONDS", "300") + cfg := minimalOperationalConfig(300, "") + + svc := newMockSecretKeyGetter(map[string]string{ + "websocket.inactivity_timeout_seconds": "600", + }) + + log := &testLogger{} + warnIfConfigDatabaseDiverges(context.Background(), svc, cfg, log) + + warns := log.logsAtLevel("WARN") + require.Len(t, warns, 1, "expected exactly one WARN log entry") + assert.Contains(t, warns[0].msg, "websocket.inactivity_timeout_seconds", + "the warning must name the affected key") + assert.Contains(t, warns[0].msg, "winner=", "the warning must say which layer is in force") + + // No value is ever logged, for any key — not just Secret-classified ones. + // Log safety must not depend on per-key classification metadata being + // correct (CodeQL go/clear-text-logging; security review of #794). + assert.NotContains(t, warns[0].msg, "300", "the config value must not be logged") + assert.NotContains(t, warns[0].msg, "600", "the database value must not be logged") +} + +// TestWarnIfConfigDatabaseDiverges_ExplicitConfigMatchesDB verifies that no warning +// is emitted when the explicit config value and the DB row agree. +func TestWarnIfConfigDatabaseDiverges_ExplicitConfigMatchesDB(t *testing.T) { + t.Setenv("TMI_WEBSOCKET_INACTIVITY_TIMEOUT_SECONDS", "300") + cfg := minimalOperationalConfig(300, "") + + svc := newMockSecretKeyGetter(map[string]string{ + "websocket.inactivity_timeout_seconds": "300", + }) + + log := &testLogger{} + warnIfConfigDatabaseDiverges(context.Background(), svc, cfg, log) + + assert.Empty(t, log.allLogs(), "expected no log output when config and database agree") +} + +// TestWarnIfConfigDatabaseDiverges_NonExplicitConfigSilent verifies that a struct +// DEFAULT (not explicitly set via env or file) disagreeing with the DB is not +// warned about — that disagreement is normal and expected. +func TestWarnIfConfigDatabaseDiverges_NonExplicitConfigSilent(t *testing.T) { + // No env var set for TMI_WEBSOCKET_INACTIVITY_TIMEOUT_SECONDS, so Explicit==false + // even though the field itself is non-zero. + cfg := minimalOperationalConfig(200, "") + + svc := newMockSecretKeyGetter(map[string]string{ + "websocket.inactivity_timeout_seconds": "999", + }) + + log := &testLogger{} + warnIfConfigDatabaseDiverges(context.Background(), svc, cfg, log) + + assert.Empty(t, log.allLogs(), "expected no log output for a non-explicit config value") +} + +// TestWarnIfConfigDatabaseDiverges_BootstrapCategorySilent verifies that a +// CategoryBootstrap key (never database-backed) is skipped even when explicit and +// even when a DB row happens to exist and disagree. +func TestWarnIfConfigDatabaseDiverges_BootstrapCategorySilent(t *testing.T) { + t.Setenv("TMI_DATABASE_URL", "postgres://explicit-host/tmi") + cfg := minimalOperationalConfig(0, "") + cfg.Database.URL = "postgres://explicit-host/tmi" + + svc := newMockSecretKeyGetter(map[string]string{ + "database.url": "postgres://some-other-host/tmi", + }) + + log := &testLogger{} + warnIfConfigDatabaseDiverges(context.Background(), svc, cfg, log) + + assert.Empty(t, log.allLogs(), "bootstrap-category keys must never be warned about") +} + +// TestWarnIfConfigDatabaseDiverges_SecretRedacted verifies that a Secret-classified +// key that diverges is warned about, but that neither the config value nor the DB +// value ever appears in the logged output. +func TestWarnIfConfigDatabaseDiverges_SecretRedacted(t *testing.T) { + const configValue = "sk-config-secret-value" + const dbValue = "sk-database-secret-value" + + t.Setenv("TMI_TIMMY_LLM_API_KEY", configValue) + cfg := minimalOperationalConfig(0, configValue) + + svc := newMockSecretKeyGetter(map[string]string{ + "timmy.llm_api_key": dbValue, + }) + + log := &testLogger{} + warnIfConfigDatabaseDiverges(context.Background(), svc, cfg, log) + + warns := log.logsAtLevel("WARN") + require.Len(t, warns, 1, "expected exactly one WARN log entry") + assert.Contains(t, warns[0].msg, "timmy.llm_api_key", "warning should name the affected key") + assert.NotContains(t, warns[0].msg, configValue, "config secret value must never be logged") + assert.NotContains(t, warns[0].msg, dbValue, "database secret value must never be logged") + + // Belt-and-suspenders: check every captured log line, not just the one WARN. + for _, entry := range log.allLogs() { + assert.NotContains(t, entry.msg, configValue) + assert.NotContains(t, entry.msg, dbValue) + } +} + +// TestWarnIfConfigDatabaseDiverges_DBReadErrorSilent verifies that a DB read error +// for a given key is skipped (best-effort) rather than panicking or warning. +func TestWarnIfConfigDatabaseDiverges_DBReadErrorSilent(t *testing.T) { + t.Setenv("TMI_WEBSOCKET_INACTIVITY_TIMEOUT_SECONDS", "300") + cfg := minimalOperationalConfig(300, "") + + svc := newMockSecretKeyGetter(map[string]string{}) + svc.errKeys["websocket.inactivity_timeout_seconds"] = fmt.Errorf("connection refused") + + log := &testLogger{} + require.NotPanics(t, func() { + warnIfConfigDatabaseDiverges(context.Background(), svc, cfg, log) + }) + + assert.Empty(t, log.logsAtLevel("WARN"), "a DB read error must not produce a warning") +} + +// TestWarnIfConfigDatabaseDiverges_AbsentOrEmptyRowSilent verifies that an absent +// DB row and an empty-value DB row are both treated as "nothing to compare" and +// produce no warning. +func TestWarnIfConfigDatabaseDiverges_AbsentOrEmptyRowSilent(t *testing.T) { + t.Setenv("TMI_WEBSOCKET_INACTIVITY_TIMEOUT_SECONDS", "300") + cfg := minimalOperationalConfig(300, "") + + // Absent row: no entry in the store at all. + svc := newMockSecretKeyGetter(map[string]string{}) + + log := &testLogger{} + warnIfConfigDatabaseDiverges(context.Background(), svc, cfg, log) + assert.Empty(t, log.allLogs(), "an absent DB row must not be warned about") + + // Empty-value row. + svc2 := newMockSecretKeyGetter(map[string]string{ + "websocket.inactivity_timeout_seconds": "", + }) + log2 := &testLogger{} + warnIfConfigDatabaseDiverges(context.Background(), svc2, cfg, log2) + assert.Empty(t, log2.allLogs(), "an empty DB row value must not be warned about") +} + +// TestWarnIfConfigDatabaseDiverges_WinnerReflectsDBOrigin verifies that the winner +// named in the message follows SystemSetting.IsExplicit(): a seeded (non-explicit) +// DB row loses to the explicit config value, while an explicit DB row wins. +func TestWarnIfConfigDatabaseDiverges_WinnerReflectsDBOrigin(t *testing.T) { + t.Setenv("TMI_WEBSOCKET_INACTIVITY_TIMEOUT_SECONDS", "300") + cfg := minimalOperationalConfig(300, "") + + t.Run("seeded DB row loses to config", func(t *testing.T) { + svc := newMockSecretKeyGetter(map[string]string{ + "websocket.inactivity_timeout_seconds": "600", + }) + svc.origins["websocket.inactivity_timeout_seconds"] = models.SystemSettingOriginSeeded + + log := &testLogger{} + warnIfConfigDatabaseDiverges(context.Background(), svc, cfg, log) + + warns := log.logsAtLevel("WARN") + require.Len(t, warns, 1) + assert.Contains(t, warns[0].msg, "winner=config") + }) + + t.Run("explicit DB row wins", func(t *testing.T) { + svc := newMockSecretKeyGetter(map[string]string{ + "websocket.inactivity_timeout_seconds": "600", + }) + svc.origins["websocket.inactivity_timeout_seconds"] = models.SystemSettingOriginExplicit + + log := &testLogger{} + warnIfConfigDatabaseDiverges(context.Background(), svc, cfg, log) + + warns := log.logsAtLevel("WARN") + require.Len(t, warns, 1) + assert.Contains(t, warns[0].msg, "winner=database") + }) +} + // TestSecretClassifiedKeys verifies that the helper returns a non-empty list // for a realistic config (regression guard). func TestSecretClassifiedKeys(t *testing.T) { @@ -314,3 +569,65 @@ func TestSecretClassifiedKeys(t *testing.T) { assert.NotEmpty(t, k, "secret key name must not be empty") } } + +// TestWarnIfConfigDatabaseDiverges_RedactsProviderClientSecret pins the fix for +// the security review's one High finding on #794. +// +// Class.Secret is FALSE for the whole auth.oauth.providers. subtree — a blanket +// true there would mis-mask non-secret sub-keys like .client_id — so secrecy +// for .client_secret is carried only by the per-setting Secret flag. The +// divergence check originally redacted on Class.Secret alone, which meant a +// rotated OAuth client secret (the very case the warning exists to surface) +// printed the old AND new secret in plaintext into logs/tmi.log on every boot. +// +// SettingsService.List decrypts before returning, so the database side was +// plaintext too. Anyone with log access got a live credential. +func TestWarnIfConfigDatabaseDiverges_RedactsProviderClientSecret(t *testing.T) { + const key = "auth.oauth.providers.google.client_secret" + const configSecret = "config-side-client-secret-DO-NOT-LOG" + const dbSecret = "database-side-client-secret-DO-NOT-LOG" + + // The key only counts as Explicit when the YAML file actually names it, + // which is how a real operator reaches this path — so load a real config + // file rather than hand-building a struct (explicitFileKeys is populated + // only by the loader). + dir := t.TempDir() + path := filepath.Join(dir, "config.yml") + require.NoError(t, os.WriteFile(path, []byte( + "auth:\n"+ + " oauth:\n"+ + " providers:\n"+ + " google:\n"+ + " id: google\n"+ + " name: Google\n"+ + " enabled: true\n"+ + " client_id: google-client-id\n"+ + " client_secret: "+configSecret+"\n"), 0o600)) + + cfg, err := config.LoadSettingsSource(path) + require.NoError(t, err) + + var found *config.MigratableSetting + for _, ms := range cfg.GetMigratableSettings() { + if ms.Key == key { + s := ms + found = &s + break + } + } + require.NotNil(t, found, "expected the loader to emit %s", key) + require.True(t, found.Explicit, "a client_secret written in YAML must be Explicit, or this test is vacuous") + require.True(t, found.IsSecret(), "client_secret must be treated as secret") + + log := &testLogger{} + svc := newMockSecretKeyGetter(map[string]string{key: dbSecret}) + + warnIfConfigDatabaseDiverges(context.Background(), svc, cfg, log) + + for _, entry := range log.allLogs() { + assert.NotContains(t, entry.msg, configSecret, + "the config-side client secret must never reach a log line") + assert.NotContains(t, entry.msg, dbSecret, + "the database-side client secret must never reach a log line") + } +} diff --git a/config-reference.md b/config-reference.md index f04efe35..8bbfe78c 100644 --- a/config-reference.md +++ b/config-reference.md @@ -1,126 +1,143 @@ # Configuration Reference - + Every TMI configuration key, grouped by category. See [[Configuration-Model]] for what the categories and columns mean. +## Precedence + +A setting's value can come from a YAML config file, an environment variable, or a row in the `system_settings` database table. Only one source wins: + +| config explicit? | DB row explicit? | winner | +|---|---|---| +| no | no | database | +| no | yes | database | +| yes | no | **config/env** | +| yes | yes | **database** | + +"Config explicit" means an operator actually supplied the value — the environment variable is set, or the key is written into the YAML file — as opposed to the value being the struct default every key carries. "DB explicit" means the row was deliberately set through the admin API or a `dbtool --import-config` run, as opposed to having been seeded with a registry default the first time the server started. + +**Bootstrap** settings are never database-backed: config/env is their only source, and the table above does not apply to them. **Operational** settings follow the table above — the database wins whenever either side is silent (the common case, since it is hot-reloadable), and config/env wins only when an operator has explicitly set it while the database row is still at its seeded default. + +**Exception:** `auth.oauth.providers.*` (and the analogous `auth.saml.providers.*` and `content_oauth.providers.*`) is a keyed collection, not a scalar, so the table above doesn't apply to it directly. It merges per provider ID instead: a config/env entry shadows a database entry with the same ID, and the database contributes any IDs that config does not define. + ## Bootstrap settings File/env only, read once at startup. Cannot come from the database. -| Key | Env var | Type | Default | Required | Secret | Description | -|-----|---------|------|---------|----------|--------|-------------| -| `alerting.enabled` | `TMI_ALERTING_ENABLED` | bool | `false` | no | no | Enable the operator-pinned audit alert sink webhook subscription (#395) | -| `auth.build_mode` | `TMI_BUILD_MODE` | string | _(none)_ | yes | no | Build mode (dev, test, production) | -| `auth.jwt.secret` | `TMI_JWT_SECRET` | string | _(secret)_ | yes | yes | JWT signing secret | -| `auth.jwt.signing_method` | `TMI_JWT_SIGNING_METHOD` | string | `HS256` | no | no | JWT signing method | -| `database.connection_pool.conn_max_idle_time` | `TMI_DB_CONN_MAX_IDLE_TIME` | int | `30` | no | no | Max connection idle time in seconds | -| `database.connection_pool.conn_max_lifetime` | `TMI_DB_CONN_MAX_LIFETIME` | int | `240` | no | no | Max connection lifetime in seconds | -| `database.connection_pool.max_idle_conns` | `TMI_DB_MAX_IDLE_CONNS` | int | `2` | no | no | Maximum idle database connections | -| `database.connection_pool.max_open_conns` | `TMI_DB_MAX_OPEN_CONNS` | int | `10` | no | no | Maximum open database connections | -| `database.redis.db` | `TMI_REDIS_DB` | int | `0` | no | no | Redis database number | -| `database.redis.host` | `TMI_REDIS_HOST` | string | `localhost` | no | no | Redis host | -| `database.redis.password` | `TMI_REDIS_PASSWORD` | string | _(secret)_ | no | yes | Redis password | -| `database.redis.port` | `TMI_REDIS_PORT` | string | `6379` | no | no | Redis port | -| `database.url` | `TMI_DATABASE_URL` | string | _(secret)_ | yes | yes | Database connection URL (password redacted) | -| `logging.also_log_to_console` | `TMI_LOG_ALSO_LOG_TO_CONSOLE` | bool | `true` | no | no | Also log to console | -| `logging.cloud_error_threshold` | `TMI_LOG_CLOUD_ERROR_THRESHOLD` | int | `5` | no | no | Cloud sink consecutive-failure threshold for one-shot Warn alarm (0 disables) | -| `logging.is_dev` | `TMI_LOG_IS_DEV` | bool | `true` | no | no | Development mode logging | -| `logging.is_test` | `TMI_LOG_IS_TEST` | bool | `false` | no | no | Test mode logging | -| `logging.level` | `TMI_LOG_LEVEL` | string | `info` | no | no | Log level | -| `logging.log_api_requests` | `TMI_LOG_API_REQUESTS` | bool | `false` | no | no | Log API requests | -| `logging.log_api_responses` | `TMI_LOG_API_RESPONSES` | bool | `false` | no | no | Log API responses | -| `logging.log_dir` | `TMI_LOG_DIR` | string | `logs` | no | no | Log directory | -| `logging.log_websocket_messages` | `TMI_LOG_WEBSOCKET_MESSAGES` | bool | `false` | no | no | Log WebSocket messages | -| `logging.max_age_days` | `TMI_LOG_MAX_AGE_DAYS` | int | `7` | no | no | Log max age in days | -| `logging.max_backups` | `TMI_LOG_MAX_BACKUPS` | int | `10` | no | no | Log max backup count | -| `logging.max_size_mb` | `TMI_LOG_MAX_SIZE_MB` | int | `100` | no | no | Log max size in MB | -| `logging.redact_auth_tokens` | `TMI_LOG_REDACT_AUTH_TOKENS` | bool | `false` | no | no | Redact auth tokens in logs | -| `logging.suppress_unauthenticated_logs` | `TMI_LOG_SUPPRESS_UNAUTH_LOGS` | bool | `true` | no | no | Suppress unauthenticated request logs | -| `secrets.provider` | `TMI_SECRETS_PROVIDER` | string | `env` | no | no | Secret provider type | -| `secrets.vault_token` | `TMI_VAULT_TOKEN` | string | _(secret)_ | no | yes | HashiCorp Vault token | -| `server.http_to_https_redirect` | `TMI_SERVER_HTTP_TO_HTTPS_REDIRECT` | bool | `true` | no | no | HTTP to HTTPS redirect | -| `server.idle_timeout` | `TMI_SERVER_IDLE_TIMEOUT` | string | `1m0s` | no | no | HTTP idle timeout | -| `server.interface` | `TMI_SERVER_INTERFACE` | string | `0.0.0.0` | yes | no | Network interface to bind to | -| `server.port` | `TMI_SERVER_PORT` | string | `8080` | yes | no | HTTP server port | -| `server.read_timeout` | `TMI_SERVER_READ_TIMEOUT` | string | `5s` | no | no | HTTP read timeout | -| `server.tls_enabled` | `TMI_SERVER_TLS_ENABLED` | bool | `false` | no | no | TLS enabled | -| `server.tls_subject_name` | `TMI_SERVER_TLS_SUBJECT_NAME` | string | `localhost` | no | no | TLS certificate subject name | -| `server.write_timeout` | `TMI_SERVER_WRITE_TIMEOUT` | string | `10s` | no | no | HTTP write timeout | +| Key | Env var | Type | Default | Required | Secret | Precedence | Description | +|-----|---------|------|---------|----------|--------|------------|-------------| +| `alerting.enabled` | `TMI_ALERTING_ENABLED` | bool | `false` | no | no | config/env only | Enable the operator-pinned audit alert sink webhook subscription (#395) | +| `auth.build_mode` | `TMI_BUILD_MODE` | string | _(none)_ | yes | no | config/env only | Build mode (dev, test, production) | +| `auth.jwt.secret` | `TMI_JWT_SECRET` | string | _(secret)_ | yes | yes | config/env only | JWT signing secret | +| `auth.jwt.signing_method` | `TMI_JWT_SIGNING_METHOD` | string | `HS256` | no | no | config/env only | JWT signing method | +| `database.connection_pool.conn_max_idle_time` | `TMI_DB_CONN_MAX_IDLE_TIME` | int | `30` | no | no | config/env only | Max connection idle time in seconds | +| `database.connection_pool.conn_max_lifetime` | `TMI_DB_CONN_MAX_LIFETIME` | int | `240` | no | no | config/env only | Max connection lifetime in seconds | +| `database.connection_pool.max_idle_conns` | `TMI_DB_MAX_IDLE_CONNS` | int | `2` | no | no | config/env only | Maximum idle database connections | +| `database.connection_pool.max_open_conns` | `TMI_DB_MAX_OPEN_CONNS` | int | `10` | no | no | config/env only | Maximum open database connections | +| `database.redis.db` | `TMI_REDIS_DB` | int | `0` | no | no | config/env only | Redis database number | +| `database.redis.host` | `TMI_REDIS_HOST` | string | `localhost` | no | no | config/env only | Redis host | +| `database.redis.password` | `TMI_REDIS_PASSWORD` | string | _(secret)_ | no | yes | config/env only | Redis password | +| `database.redis.port` | `TMI_REDIS_PORT` | string | `6379` | no | no | config/env only | Redis port | +| `database.url` | `TMI_DATABASE_URL` | string | _(secret)_ | yes | yes | config/env only | Database connection URL (password redacted) | +| `logging.also_log_to_console` | `TMI_LOG_ALSO_LOG_TO_CONSOLE` | bool | `true` | no | no | config/env only | Also log to console | +| `logging.cloud_error_threshold` | `TMI_LOG_CLOUD_ERROR_THRESHOLD` | int | `5` | no | no | config/env only | Cloud sink consecutive-failure threshold for one-shot Warn alarm (0 disables) | +| `logging.is_dev` | `TMI_LOG_IS_DEV` | bool | `true` | no | no | config/env only | Development mode logging | +| `logging.is_test` | `TMI_LOG_IS_TEST` | bool | `false` | no | no | config/env only | Test mode logging | +| `logging.level` | `TMI_LOG_LEVEL` | string | `info` | no | no | config/env only | Log level | +| `logging.log_api_requests` | `TMI_LOG_API_REQUESTS` | bool | `false` | no | no | config/env only | Log API requests | +| `logging.log_api_responses` | `TMI_LOG_API_RESPONSES` | bool | `false` | no | no | config/env only | Log API responses | +| `logging.log_dir` | `TMI_LOG_DIR` | string | `logs` | no | no | config/env only | Log directory | +| `logging.log_websocket_messages` | `TMI_LOG_WEBSOCKET_MESSAGES` | bool | `false` | no | no | config/env only | Log WebSocket messages | +| `logging.max_age_days` | `TMI_LOG_MAX_AGE_DAYS` | int | `7` | no | no | config/env only | Log max age in days | +| `logging.max_backups` | `TMI_LOG_MAX_BACKUPS` | int | `10` | no | no | config/env only | Log max backup count | +| `logging.max_size_mb` | `TMI_LOG_MAX_SIZE_MB` | int | `100` | no | no | config/env only | Log max size in MB | +| `logging.redact_auth_tokens` | `TMI_LOG_REDACT_AUTH_TOKENS` | bool | `false` | no | no | config/env only | Redact auth tokens in logs | +| `logging.suppress_unauthenticated_logs` | `TMI_LOG_SUPPRESS_UNAUTH_LOGS` | bool | `true` | no | no | config/env only | Suppress unauthenticated request logs | +| `secrets.provider` | `TMI_SECRETS_PROVIDER` | string | `env` | no | no | config/env only | Secret provider type | +| `secrets.vault_token` | `TMI_VAULT_TOKEN` | string | _(secret)_ | no | yes | config/env only | HashiCorp Vault token | +| `server.http_to_https_redirect` | `TMI_SERVER_HTTP_TO_HTTPS_REDIRECT` | bool | `true` | no | no | config/env only | HTTP to HTTPS redirect | +| `server.idle_timeout` | `TMI_SERVER_IDLE_TIMEOUT` | string | `1m0s` | no | no | config/env only | HTTP idle timeout | +| `server.interface` | `TMI_SERVER_INTERFACE` | string | `0.0.0.0` | yes | no | config/env only | Network interface to bind to | +| `server.port` | `TMI_SERVER_PORT` | string | `8080` | yes | no | config/env only | HTTP server port | +| `server.read_timeout` | `TMI_SERVER_READ_TIMEOUT` | string | `5s` | no | no | config/env only | HTTP read timeout | +| `server.tls_enabled` | `TMI_SERVER_TLS_ENABLED` | bool | `false` | no | no | config/env only | TLS enabled | +| `server.tls_subject_name` | `TMI_SERVER_TLS_SUBJECT_NAME` | string | `localhost` | no | no | config/env only | TLS certificate subject name | +| `server.write_timeout` | `TMI_SERVER_WRITE_TIMEOUT` | string | `10s` | no | no | config/env only | HTTP write timeout | ## Operational settings DB-backed, seeded from defaults on first run, editable at runtime via `/admin/settings`. -| Key | Type | Default | Mutability | Visibility | Secret | Description | -|-----|------|---------|------------|------------|--------|-------------| -| `auth.auto_promote_first_user` | bool | `false` | hot | admin-only | no | Auto-promote first user to admin | -| `auth.cookie.domain` | string | _(none)_ | hot | admin-only | no | Cookie domain | -| `auth.cookie.enabled` | bool | `true` | hot | admin-only | no | HttpOnly cookie-based auth enabled | -| `auth.cookie.secure` | bool | `false` | hot | admin-only | no | Require HTTPS for cookies | -| `auth.everyone_is_a_reviewer` | bool | `false` | hot | admin-only | no | Auto-add all users to Security Reviewers group | -| `auth.jwt.expiration_seconds` | int | `3600` | hot | admin-only | no | JWT token expiration in seconds | -| `auth.jwt.refresh_token_days` | int | `7` | hot | admin-only | no | Refresh token TTL in days | -| `auth.jwt.session_lifetime_days` | int | `7` | hot | admin-only | no | Absolute session lifetime in days | -| `auth.oauth_callback_url` | string | `http://localhost:8080/oauth2/callback` | hot | admin-only | no | OAuth callback URL | -| `auth.step_up_window_seconds` | int | `300` | hot | admin-only | no | Step-up auth_time freshness window in seconds for /admin/* writes (#355); minimum 60 | -| `content_extractors.compressed_size_bytes` | int | `20971520` | hot | admin-only | no | Max compressed upload size in bytes | -| `content_extractors.decompressed_size_bytes` | int | `52428800` | hot | admin-only | no | Max decompressed content size in bytes | -| `content_extractors.markdown_size_bytes` | int | `131072` | hot | admin-only | no | Max markdown output size in bytes | -| `content_extractors.part_size_bytes` | int | `20971520` | hot | admin-only | no | Max size of a single archive part in bytes | -| `content_extractors.per_user_concurrency_default` | int | `2` | hot | admin-only | no | Default max concurrent extractions per user | -| `content_extractors.pptx_slides` | int | `100` | hot | admin-only | no | Max number of PowerPoint slides to extract | -| `content_extractors.wall_clock_budget` | string | `30s` | hot | admin-only | no | Max wall-clock time for a single extraction | -| `content_extractors.xlsx_cells` | int | `1000` | hot | admin-only | no | Max number of Excel cells to extract | -| `content_sources.confluence.enabled` | bool | `false` | hot | admin-only | no | Confluence content source enabled | -| `content_sources.google_drive.enabled` | bool | `false` | hot | admin-only | no | Google Drive content source enabled | -| `content_sources.google_workspace.enabled` | bool | `false` | hot | admin-only | no | Google Workspace content source enabled | -| `content_sources.microsoft.enabled` | bool | `false` | hot | admin-only | no | Microsoft content source enabled | -| `extraction.async_enabled` | bool | `false` | hot | admin-only | no | Route document extraction through the async worker pipeline instead of inline (default false; requires NATS) | -| `features.saml_enabled` | bool | `false` | hot | public | no | Enable SAML authentication | -| `observability.enabled` | bool | `false` | hot | admin-only | no | OpenTelemetry tracing enabled | -| `observability.prometheus_port` | int | `0` | hot | admin-only | no | Prometheus metrics port (0 = disabled) | -| `observability.sampling_rate` | float | `1` | hot | admin-only | no | OpenTelemetry trace sampling rate (0.0–1.0) | -| `server.disable_rate_limiting` | bool | `false` | hot | admin-only | no | Disable all rate limiting (dev/test only) | -| `server.ratelimit_public_rpm` | int | `0` | hot | admin-only | no | Requests per minute per IP for public endpoints | -| `server.require_if_match` | bool | `false` | hot | admin-only | no | Return 428 when If-Match header is missing on PUT/PATCH | -| `session.timeout_minutes` | int | `60` | hot | admin-only | no | JWT token expiration in minutes | -| `timmy.chunk_overlap` | int | `50` | hot | admin-only | no | Embedding chunk overlap | -| `timmy.chunk_size` | int | `512` | hot | admin-only | no | Embedding chunk size | -| `timmy.code_embedding_api_key` | string | _(secret)_ | hot | admin-only | yes | Code embedding API key | -| `timmy.code_embedding_base_url` | string | _(none)_ | hot | admin-only | no | Code embedding API base URL | -| `timmy.code_embedding_model` | string | _(none)_ | hot | admin-only | no | Code embedding model | -| `timmy.code_embedding_provider` | string | _(none)_ | hot | admin-only | no | Code embedding provider | -| `timmy.code_retrieval_top_k` | int | `10` | hot | admin-only | no | Code retrieval top-k results | -| `timmy.dump_extracted_text_to_note` | bool | `false` | hot | admin-only | no | Dump extracted text to note (dev/test only) | -| `timmy.embedding_cleanup_interval_minutes` | int | `60` | hot | admin-only | no | Embedding cleanup interval in minutes | -| `timmy.embedding_dimension` | int | `0` | hot | admin-only | no | Text embedding vector dimension — shared invariant | -| `timmy.embedding_idle_days_active` | int | `30` | hot | admin-only | no | Days before idle active-TM embeddings are cleaned up | -| `timmy.embedding_idle_days_closed` | int | `7` | hot | admin-only | no | Days before idle closed-TM embeddings are cleaned up | -| `timmy.enabled` | bool | `false` | hot | admin-only | no | Timmy AI assistant enabled | -| `timmy.inactivity_timeout_seconds` | int | `3600` | hot | admin-only | no | Session inactivity timeout in seconds | -| `timmy.llm_api_key` | string | _(secret)_ | hot | admin-only | yes | LLM API key | -| `timmy.llm_base_url` | string | _(none)_ | hot | admin-only | no | LLM API base URL | -| `timmy.llm_max_tokens` | int | `4096` | hot | admin-only | no | Max tokens per chat completion (required by Anthropic; optional for OpenAI) | -| `timmy.llm_model` | string | _(none)_ | hot | admin-only | no | LLM model | -| `timmy.llm_provider` | string | _(none)_ | hot | admin-only | no | LLM provider | -| `timmy.llm_timeout_seconds` | int | `120` | hot | admin-only | no | LLM request timeout in seconds | -| `timmy.max_concurrent_llm_requests` | int | `10` | hot | admin-only | no | Max concurrent LLM requests | -| `timmy.max_conversation_history` | int | `50` | hot | admin-only | no | Max conversation history entries | -| `timmy.max_memory_mb` | int | `256` | hot | admin-only | no | Max memory in MB | -| `timmy.max_messages_per_user_per_hour` | int | `60` | hot | admin-only | no | Max messages per user per hour | -| `timmy.max_sessions_per_threat_model` | int | `50` | hot | admin-only | no | Max Timmy sessions per threat model | -| `timmy.operator_system_prompt` | string | _(none)_ | hot | admin-only | no | Operator system prompt override | -| `timmy.query_decomposition_enabled` | bool | `false` | hot | admin-only | no | Query decomposition enabled | -| `timmy.rerank_api_key` | string | _(secret)_ | hot | admin-only | yes | Reranker API key | -| `timmy.rerank_base_url` | string | _(none)_ | hot | admin-only | no | Reranker API base URL | -| `timmy.rerank_model` | string | _(none)_ | hot | admin-only | no | Reranker model | -| `timmy.rerank_provider` | string | _(none)_ | hot | admin-only | no | Reranker provider | -| `timmy.rerank_top_k` | int | `10` | hot | admin-only | no | Reranker top-k results | -| `timmy.text_embedding_api_key` | string | _(secret)_ | hot | admin-only | yes | Text embedding API key | -| `timmy.text_embedding_base_url` | string | _(none)_ | hot | admin-only | no | Text embedding API base URL — shared invariant | -| `timmy.text_embedding_model` | string | _(none)_ | hot | admin-only | no | Text embedding model — shared invariant between ingest and query | -| `timmy.text_embedding_provider` | string | _(none)_ | hot | admin-only | no | Text embedding provider | -| `timmy.text_retrieval_top_k` | int | `10` | hot | admin-only | no | Text retrieval top-k results | -| `webhooks.allow_http_targets` | bool | `false` | hot | admin-only | no | Allow non-HTTPS webhook target URLs (intra-cluster use only) | -| `websocket.inactivity_timeout_seconds` | int | `300` | hot | admin-only | no | WebSocket inactivity timeout in seconds | +| Key | Type | Default | Mutability | Visibility | Secret | Precedence | Description | +|-----|------|---------|------------|------------|--------|------------|-------------| +| `auth.auto_promote_first_user` | bool | `false` | hot | admin-only | no | db unless config explicit | Auto-promote first user to admin | +| `auth.cookie.domain` | string | _(none)_ | hot | admin-only | no | db unless config explicit | Cookie domain | +| `auth.cookie.enabled` | bool | `true` | hot | admin-only | no | db unless config explicit | HttpOnly cookie-based auth enabled | +| `auth.cookie.secure` | bool | `false` | hot | admin-only | no | db unless config explicit | Require HTTPS for cookies | +| `auth.everyone_is_a_reviewer` | bool | `false` | hot | admin-only | no | db unless config explicit | Auto-add all users to Security Reviewers group | +| `auth.jwt.expiration_seconds` | int | `3600` | hot | admin-only | no | db unless config explicit | JWT token expiration in seconds | +| `auth.jwt.refresh_token_days` | int | `7` | hot | admin-only | no | db unless config explicit | Refresh token TTL in days | +| `auth.jwt.session_lifetime_days` | int | `7` | hot | admin-only | no | db unless config explicit | Absolute session lifetime in days | +| `auth.oauth_callback_url` | string | `http://localhost:8080/oauth2/callback` | hot | admin-only | no | db unless config explicit | OAuth callback URL | +| `auth.step_up_window_seconds` | int | `300` | hot | admin-only | no | db unless config explicit | Step-up auth_time freshness window in seconds for /admin/* writes (#355); minimum 60 | +| `content_extractors.compressed_size_bytes` | int | `20971520` | hot | admin-only | no | db unless config explicit | Max compressed upload size in bytes | +| `content_extractors.decompressed_size_bytes` | int | `52428800` | hot | admin-only | no | db unless config explicit | Max decompressed content size in bytes | +| `content_extractors.markdown_size_bytes` | int | `131072` | hot | admin-only | no | db unless config explicit | Max markdown output size in bytes | +| `content_extractors.part_size_bytes` | int | `20971520` | hot | admin-only | no | db unless config explicit | Max size of a single archive part in bytes | +| `content_extractors.per_user_concurrency_default` | int | `2` | hot | admin-only | no | db unless config explicit | Default max concurrent extractions per user | +| `content_extractors.pptx_slides` | int | `100` | hot | admin-only | no | db unless config explicit | Max number of PowerPoint slides to extract | +| `content_extractors.wall_clock_budget` | string | `30s` | hot | admin-only | no | db unless config explicit | Max wall-clock time for a single extraction | +| `content_extractors.xlsx_cells` | int | `1000` | hot | admin-only | no | db unless config explicit | Max number of Excel cells to extract | +| `content_sources.confluence.enabled` | bool | `false` | hot | admin-only | no | db unless config explicit | Confluence content source enabled | +| `content_sources.google_drive.enabled` | bool | `false` | hot | admin-only | no | db unless config explicit | Google Drive content source enabled | +| `content_sources.google_workspace.enabled` | bool | `false` | hot | admin-only | no | db unless config explicit | Google Workspace content source enabled | +| `content_sources.microsoft.enabled` | bool | `false` | hot | admin-only | no | db unless config explicit | Microsoft content source enabled | +| `extraction.async_enabled` | bool | `false` | hot | admin-only | no | db unless config explicit | Route document extraction through the async worker pipeline instead of inline (default false; requires NATS) | +| `features.saml_enabled` | bool | `false` | hot | public | no | db unless config explicit | Enable SAML authentication | +| `observability.enabled` | bool | `false` | hot | admin-only | no | db unless config explicit | OpenTelemetry tracing enabled | +| `observability.prometheus_port` | int | `0` | hot | admin-only | no | db unless config explicit | Prometheus metrics port (0 = disabled) | +| `observability.sampling_rate` | float | `1` | hot | admin-only | no | db unless config explicit | OpenTelemetry trace sampling rate (0.0–1.0) | +| `server.disable_rate_limiting` | bool | `false` | hot | admin-only | no | db unless config explicit | Disable all rate limiting (dev/test only) | +| `server.ratelimit_public_rpm` | int | `0` | hot | admin-only | no | db unless config explicit | Requests per minute per IP for public endpoints | +| `server.require_if_match` | bool | `false` | hot | admin-only | no | db unless config explicit | Return 428 when If-Match header is missing on PUT/PATCH | +| `session.timeout_minutes` | int | `60` | hot | admin-only | no | db unless config explicit | JWT token expiration in minutes | +| `timmy.chunk_overlap` | int | `50` | hot | admin-only | no | db unless config explicit | Embedding chunk overlap | +| `timmy.chunk_size` | int | `512` | hot | admin-only | no | db unless config explicit | Embedding chunk size | +| `timmy.code_embedding_api_key` | string | _(secret)_ | hot | admin-only | yes | db unless config explicit | Code embedding API key | +| `timmy.code_embedding_base_url` | string | _(none)_ | hot | admin-only | no | db unless config explicit | Code embedding API base URL | +| `timmy.code_embedding_model` | string | _(none)_ | hot | admin-only | no | db unless config explicit | Code embedding model | +| `timmy.code_embedding_provider` | string | _(none)_ | hot | admin-only | no | db unless config explicit | Code embedding provider | +| `timmy.code_retrieval_top_k` | int | `10` | hot | admin-only | no | db unless config explicit | Code retrieval top-k results | +| `timmy.dump_extracted_text_to_note` | bool | `false` | hot | admin-only | no | db unless config explicit | Dump extracted text to note (dev/test only) | +| `timmy.embedding_cleanup_interval_minutes` | int | `60` | hot | admin-only | no | db unless config explicit | Embedding cleanup interval in minutes | +| `timmy.embedding_dimension` | int | `0` | hot | admin-only | no | db unless config explicit | Text embedding vector dimension — shared invariant | +| `timmy.embedding_idle_days_active` | int | `30` | hot | admin-only | no | db unless config explicit | Days before idle active-TM embeddings are cleaned up | +| `timmy.embedding_idle_days_closed` | int | `7` | hot | admin-only | no | db unless config explicit | Days before idle closed-TM embeddings are cleaned up | +| `timmy.enabled` | bool | `false` | hot | admin-only | no | db unless config explicit | Timmy AI assistant enabled | +| `timmy.inactivity_timeout_seconds` | int | `3600` | hot | admin-only | no | db unless config explicit | Session inactivity timeout in seconds | +| `timmy.llm_api_key` | string | _(secret)_ | hot | admin-only | yes | db unless config explicit | LLM API key | +| `timmy.llm_base_url` | string | _(none)_ | hot | admin-only | no | db unless config explicit | LLM API base URL | +| `timmy.llm_max_tokens` | int | `4096` | hot | admin-only | no | db unless config explicit | Max tokens per chat completion (required by Anthropic; optional for OpenAI) | +| `timmy.llm_model` | string | _(none)_ | hot | admin-only | no | db unless config explicit | LLM model | +| `timmy.llm_provider` | string | _(none)_ | hot | admin-only | no | db unless config explicit | LLM provider | +| `timmy.llm_timeout_seconds` | int | `120` | hot | admin-only | no | db unless config explicit | LLM request timeout in seconds | +| `timmy.max_concurrent_llm_requests` | int | `10` | hot | admin-only | no | db unless config explicit | Max concurrent LLM requests | +| `timmy.max_conversation_history` | int | `50` | hot | admin-only | no | db unless config explicit | Max conversation history entries | +| `timmy.max_memory_mb` | int | `256` | hot | admin-only | no | db unless config explicit | Max memory in MB | +| `timmy.max_messages_per_user_per_hour` | int | `60` | hot | admin-only | no | db unless config explicit | Max messages per user per hour | +| `timmy.max_sessions_per_threat_model` | int | `50` | hot | admin-only | no | db unless config explicit | Max Timmy sessions per threat model | +| `timmy.operator_system_prompt` | string | _(none)_ | hot | admin-only | no | db unless config explicit | Operator system prompt override | +| `timmy.query_decomposition_enabled` | bool | `false` | hot | admin-only | no | db unless config explicit | Query decomposition enabled | +| `timmy.rerank_api_key` | string | _(secret)_ | hot | admin-only | yes | db unless config explicit | Reranker API key | +| `timmy.rerank_base_url` | string | _(none)_ | hot | admin-only | no | db unless config explicit | Reranker API base URL | +| `timmy.rerank_model` | string | _(none)_ | hot | admin-only | no | db unless config explicit | Reranker model | +| `timmy.rerank_provider` | string | _(none)_ | hot | admin-only | no | db unless config explicit | Reranker provider | +| `timmy.rerank_top_k` | int | `10` | hot | admin-only | no | db unless config explicit | Reranker top-k results | +| `timmy.text_embedding_api_key` | string | _(secret)_ | hot | admin-only | yes | db unless config explicit | Text embedding API key | +| `timmy.text_embedding_base_url` | string | _(none)_ | hot | admin-only | no | db unless config explicit | Text embedding API base URL — shared invariant | +| `timmy.text_embedding_model` | string | _(none)_ | hot | admin-only | no | db unless config explicit | Text embedding model — shared invariant between ingest and query | +| `timmy.text_embedding_provider` | string | _(none)_ | hot | admin-only | no | db unless config explicit | Text embedding provider | +| `timmy.text_retrieval_top_k` | int | `10` | hot | admin-only | no | db unless config explicit | Text retrieval top-k results | +| `webhooks.allow_http_targets` | bool | `false` | hot | admin-only | no | db unless config explicit | Allow non-HTTPS webhook target URLs (intra-cluster use only) | +| `websocket.inactivity_timeout_seconds` | int | `300` | hot | admin-only | no | db unless config explicit | WebSocket inactivity timeout in seconds | diff --git a/internal/config/migratable_settings.go b/internal/config/migratable_settings.go index f561656a..4ddfcdd5 100644 --- a/internal/config/migratable_settings.go +++ b/internal/config/migratable_settings.go @@ -28,6 +28,24 @@ type MigratableSetting struct { Explicit bool } +// IsSecret reports whether this setting's value must never be shown in an API +// response or written to a log. +// +// Consult this rather than either flag directly. The two are NOT equivalent and +// the difference is a live trap: Class.Secret is false for the whole +// `auth.oauth.providers.`, `auth.saml.providers.` and `content_oauth.providers.` +// subtrees (see prefixClassifications — a blanket true there would mis-mask +// non-secret sub-keys like .client_id), so for those subtrees secrecy is +// carried ONLY by the per-setting Secret flag that the provider helpers set on +// .client_secret, .sp_private_key and .idp_metadata_b64xml. The +// Class.Secret->Secret sync in GetMigratableSettings runs in one direction +// only, so checking Class.Secret alone silently treats a real OAuth client +// secret as public. +// SEM@2daf3be663df9da54323f16d115f12d78d435c3f: report whether a setting's value must be masked in responses and logs (pure) +func (m MigratableSetting) IsSecret() bool { + return m.Secret || m.Class.Secret +} + // settingSource returns "environment" if the given env var is set, otherwise "config". // // The env var named here must be one this package's struct tags actually bind. @@ -508,7 +526,7 @@ func (c *Config) getMigratableSecretsSettings() []MigratableSetting { // getMigratableTimmySettings returns Timmy AI assistant settings, including // the shared embedding profile keys. -// SEM@2efe458db50a86b8f77bc2b6f5938a5d15cc4315: build migratable settings list for Timmy AI assistant LLM, embedding, and session parameters (pure) +// SEM@f7cc4344884e20bc7f6fb9a5815e2e1d530c0ff6: build migratable settings list for Timmy AI assistant LLM, embedding, and session parameters (pure) func (c *Config) getMigratableTimmySettings() []MigratableSetting { t := c.Timmy settings := []MigratableSetting{ @@ -585,7 +603,7 @@ func (c *Config) getMigratableAdministratorsSettings() []MigratableSetting { } // getMigratableObservabilitySettings returns OpenTelemetry / Prometheus settings. -// SEM@2efe458db50a86b8f77bc2b6f5938a5d15cc4315: build migratable settings list for OpenTelemetry and Prometheus observability parameters (pure) +// SEM@1a4ca5f99be4a25df66b2836e9b9f4c87628184a: build migratable settings list for OpenTelemetry and Prometheus observability parameters (pure) func (c *Config) getMigratableObservabilitySettings() []MigratableSetting { return []MigratableSetting{ {Key: "observability.enabled", Value: strconv.FormatBool(c.Observability.Enabled), Type: "bool", Description: "OpenTelemetry tracing enabled", Source: settingSource("TMI_OTEL_ENABLED"), EnvVar: "TMI_OTEL_ENABLED"}, diff --git a/internal/config/reference_gen.go b/internal/config/reference_gen.go index 400cd342..07c53df2 100644 --- a/internal/config/reference_gen.go +++ b/internal/config/reference_gen.go @@ -8,10 +8,11 @@ import ( ) // GenerateReferenceMarkdown produces the per-key configuration reference from -// the classification registry. It emits two tables — bootstrap and operational -// — so the wiki Configuration-Reference page is generated, not hand-maintained. -// Secret defaults are shown as vault:// placeholders, never real values. -// SEM@15870ed6c51c35dd0b30cf6f7a777a7010b1b69e: build the wiki configuration reference as Markdown with bootstrap and operational tables (pure) +// the classification registry. It emits a precedence explainer plus two +// tables — bootstrap and operational — so the wiki Configuration-Reference +// page is generated, not hand-maintained. Secret defaults are shown as +// vault:// placeholders, never real values. +// SEM@0000000000000000000000000000000000000000: build the wiki configuration reference as Markdown with a precedence explainer and bootstrap/operational tables (pure) func GenerateReferenceMarkdown() ([]byte, error) { cfg := getDefaultConfig() cfg.Server.TLSSubjectName = "localhost" // deterministic — must not embed the build host's name @@ -37,18 +38,20 @@ func GenerateReferenceMarkdown() ([]byte, error) { "[[Configuration-Model]] for what the categories and columns mean.\n\n", time.Now().UTC().Format(time.RFC3339)) + b.WriteString(precedenceSection()) + b.WriteString("## Bootstrap settings\n\n") b.WriteString("File/env only, read once at startup. Cannot come from the database.\n\n") - b.WriteString("| Key | Env var | Type | Default | Required | Secret | Description |\n") - b.WriteString("|-----|---------|------|---------|----------|--------|-------------|\n") + b.WriteString("| Key | Env var | Type | Default | Required | Secret | Precedence | Description |\n") + b.WriteString("|-----|---------|------|---------|----------|--------|------------|-------------|\n") for _, s := range bootstrap { b.WriteString(bootstrapRow(s)) } b.WriteString("\n## Operational settings\n\n") b.WriteString("DB-backed, seeded from defaults on first run, editable at runtime via `/admin/settings`.\n\n") - b.WriteString("| Key | Type | Default | Mutability | Visibility | Secret | Description |\n") - b.WriteString("|-----|------|---------|------------|------------|--------|-------------|\n") + b.WriteString("| Key | Type | Default | Mutability | Visibility | Secret | Precedence | Description |\n") + b.WriteString("|-----|------|---------|------------|------------|--------|------------|-------------|\n") for _, s := range operational { b.WriteString(operationalRow(s)) } @@ -56,18 +59,66 @@ func GenerateReferenceMarkdown() ([]byte, error) { return []byte(b.String()), nil } -// SEM@15870ed6c51c35dd0b30cf6f7a777a7010b1b69e: format a bootstrap config setting as a Markdown table row (pure) +// precedenceSection is the prose explainer for which source (config/env vs. +// the system_settings database row) wins for a given key, placed above the +// tables so an operator debugging a config problem doesn't have to read +// source to find the rule. See #794. +// SEM@0000000000000000000000000000000000000000: render the config/env-vs-database precedence rule as a Markdown section (pure) +func precedenceSection() string { + return "## Precedence\n\n" + + "A setting's value can come from a YAML config file, an environment " + + "variable, or a row in the `system_settings` database table. Only one " + + "source wins:\n\n" + + "| config explicit? | DB row explicit? | winner |\n" + + "|---|---|---|\n" + + "| no | no | database |\n" + + "| no | yes | database |\n" + + "| yes | no | **config/env** |\n" + + "| yes | yes | **database** |\n\n" + + "\"Config explicit\" means an operator actually supplied the value — " + + "the environment variable is set, or the key is written into the YAML " + + "file — as opposed to the value being the struct default every key " + + "carries. \"DB explicit\" means the row was deliberately set through " + + "the admin API or a `dbtool --import-config` run, as opposed to having " + + "been seeded with a registry default the first time the server " + + "started.\n\n" + + "**Bootstrap** settings are never database-backed: config/env is their " + + "only source, and the table above does not apply to them. " + + "**Operational** settings follow the table above — the database wins " + + "whenever either side is silent (the common case, since it is " + + "hot-reloadable), and config/env wins only when an operator has " + + "explicitly set it while the database row is still at its seeded " + + "default.\n\n" + + "**Exception:** `auth.oauth.providers.*` (and the analogous " + + "`auth.saml.providers.*` and `content_oauth.providers.*`) is a keyed " + + "collection, not a scalar, so the table above doesn't apply to it " + + "directly. It merges per provider ID instead: a config/env entry " + + "shadows a database entry with the same ID, and the database " + + "contributes any IDs that config does not define.\n\n" +} + +// SEM@0000000000000000000000000000000000000000: format a bootstrap config setting as a Markdown table row (pure) func bootstrapRow(s MigratableSetting) string { - return fmt.Sprintf("| `%s` | %s | %s | %s | %s | %s | %s |\n", + return fmt.Sprintf("| `%s` | %s | %s | %s | %s | %s | %s | %s |\n", s.Key, codeOrDash(s.EnvVar), s.Type, defaultCell(s), - yesNo(s.Class.Required), yesNo(s.Class.Secret), sanitizeCell(s.Description)) + yesNo(s.Class.Required), yesNo(s.Class.Secret), precedenceCell(s), sanitizeCell(s.Description)) } -// SEM@15870ed6c51c35dd0b30cf6f7a777a7010b1b69e: format an operational config setting as a Markdown table row (pure) +// SEM@0000000000000000000000000000000000000000: format an operational config setting as a Markdown table row (pure) func operationalRow(s MigratableSetting) string { - return fmt.Sprintf("| `%s` | %s | %s | %s | %s | %s | %s |\n", + return fmt.Sprintf("| `%s` | %s | %s | %s | %s | %s | %s | %s |\n", s.Key, s.Type, defaultCell(s), s.Class.Mutability.String(), - s.Class.Visibility.String(), yesNo(s.Class.Secret), sanitizeCell(s.Description)) + s.Class.Visibility.String(), yesNo(s.Class.Secret), precedenceCell(s), sanitizeCell(s.Description)) +} + +// precedenceCell renders the terse per-row precedence summary; the full rule +// is spelled out once in precedenceSection rather than repeated per row. +// SEM@0000000000000000000000000000000000000000: summarize which source wins for a setting's category as a table cell (pure) +func precedenceCell(s MigratableSetting) string { + if s.Class.Category == CategoryBootstrap { + return "config/env only" + } + return "db unless config explicit" } // SEM@15870ed6c51c35dd0b30cf6f7a777a7010b1b69e: format a config setting's default value, masking secrets as placeholder (pure) diff --git a/internal/config/reference_gen_test.go b/internal/config/reference_gen_test.go index c185b312..a07d48f3 100644 --- a/internal/config/reference_gen_test.go +++ b/internal/config/reference_gen_test.go @@ -54,6 +54,56 @@ func TestGenerateReferenceMarkdown_NeverLeaksSecretDefault(t *testing.T) { } } +func TestGenerateReferenceMarkdown_HasPrecedenceSection(t *testing.T) { + out, err := GenerateReferenceMarkdown() + if err != nil { + t.Fatalf("GenerateReferenceMarkdown: %v", err) + } + s := string(out) + for _, want := range []string{ + "## Precedence", + "config explicit", + "DB explicit", + "auth.oauth.providers.*", + } { + if !strings.Contains(s, want) { + t.Errorf("reference missing precedence prose %q", want) + } + } +} + +func TestGenerateReferenceMarkdown_PrecedenceColumnByCategory(t *testing.T) { + out, err := GenerateReferenceMarkdown() + if err != nil { + t.Fatalf("GenerateReferenceMarkdown: %v", err) + } + s := string(out) + + // server.port is bootstrap: config/env only, never DB-backed. + bootstrapLine := findRowContaining(t, s, "`server.port`") + if !strings.Contains(bootstrapLine, "config/env only") { + t.Errorf("bootstrap key server.port: want precedence cell %q, got row %q", "config/env only", bootstrapLine) + } + + // auth.auto_promote_first_user is operational: DB wins unless config is explicit. + operationalLine := findRowContaining(t, s, "`auth.auto_promote_first_user`") + if !strings.Contains(operationalLine, "db unless config explicit") { + t.Errorf("operational key auth.auto_promote_first_user: want precedence cell %q, got row %q", + "db unless config explicit", operationalLine) + } +} + +func findRowContaining(t *testing.T, doc, keyCell string) string { + t.Helper() + for _, line := range strings.Split(doc, "\n") { + if strings.Contains(line, keyCell) { + return line + } + } + t.Fatalf("no table row found containing %q", keyCell) + return "" +} + func TestConfigReferenceFile_MatchesRegistry(t *testing.T) { generated, err := GenerateReferenceMarkdown() if err != nil { diff --git a/internal/dbschema/system_setting_origin_backfill.go b/internal/dbschema/system_setting_origin_backfill.go new file mode 100644 index 00000000..d78ad157 --- /dev/null +++ b/internal/dbschema/system_setting_origin_backfill.go @@ -0,0 +1,375 @@ +// Package dbschema: one-time backfill of system_settings.origin for rows +// that predate the column, plus the CHECK constraint that enforces its +// invariant going forward (#794). +// +// SystemSetting.Origin distinguishes a row SeedDefaults inserted from one an +// operator deliberately set (api/models/system_setting.go). NULL reads as +// SEEDED -- the fail-safe direction for data loss -- but that same polarity +// is wrong for a row that already existed before this column shipped: on +// every live deployment (RDS, k3s) a pre-existing row was, under the old +// rules, either the only value the operator could see or one they had +// genuinely edited via the admin API. Leaving it NULL after upgrade would +// silently hand its authority to config, which is exactly the class of +// regression #794 introduced this column to prevent (oracle-db-admin +// review: the change is otherwise inert on the deployments that had the +// 2026-08-20 outage, because SeedDefaults skips rows that already exist and +// nothing else ever stamps a pre-existing row). +// +// BackfillSystemSettingOrigin stamps a NULL-origin row explicit only when it +// shows operator intent: modified_by is set (an admin API write, or a +// dbtool import, from before Origin existed), or its value no longer +// matches what SeedDefaults would seed for that key today. Every other +// NULL-origin row -- one that still holds the untouched registry default -- +// is left alone, so it correctly reads as seeded going forward. This is +// deliberately not a blanket `UPDATE ... SET origin = 'seeded' WHERE origin +// IS NULL`: that would demote genuinely admin-set rows the moment their +// value happens to equal the current default. +// +// The value-comparison signal cannot see through at-rest encryption (see +// expectedSeedValues): an encrypted row is left on the modified_by signal +// alone and otherwise stays NULL. That is a deliberate choice, not an +// oversight -- oracle-db-admin review, #794. Defaulting an +// encrypted-and-unmatched row to explicit would have reproduced the +// 2026-08-20 outage class on every encrypted deployment (a seeded, encrypted +// auth.oauth_callback_url row would keep outranking a correctly-set env +// var, exactly as it did before this column existed), so this backfill +// prefers "correctly seeded" as the default reading for a row it cannot +// evaluate, at the cost of a narrow, accepted gap: a pre-#794 +// dbtool-imported encrypted value that never recorded modified_by (dbtool's +// import path did not stamp it before Origin existed) reads as seeded +// rather than explicit after upgrade. Every writer as of #794 -- +// SettingsService.Set, SeedDefaults, and dbtool's config-import and seed +// paths -- stamps Origin directly, so this gap cannot recur going forward. +package dbschema + +import ( + "fmt" + "strings" + + "github.com/ericfitz/tmi/api/models" + "github.com/ericfitz/tmi/internal/config" + "github.com/ericfitz/tmi/internal/crypto" + "github.com/ericfitz/tmi/internal/dberrors" + "github.com/ericfitz/tmi/internal/slogging" + "gorm.io/gorm" +) + +// systemSettingOriginCheckName is the CHECK constraint enforcing that +// system_settings.origin is NULL, "seeded", or "explicit" -- never any other +// value, and never the empty string (which Oracle binds as NULL anyway). +const systemSettingOriginCheckName = "chk_system_settings_origin" + +// expectedSeedValues mirrors SettingsService.SeedDefaults's key/value +// construction (api/settings_service.go) exactly: the explicit +// models.DefaultSystemSettings() entries, plus config.DefaultOperationalSettings() +// entries for keys not already covered (empty config values are never +// seeded, and an explicit default wins on collision). The result is "what +// SeedDefaults would insert today for a database with none of these rows", +// which is what a NULL-origin row's value is compared against to decide +// whether an operator has since changed it. +// +// This is a plaintext comparison and is never meaningful against an +// at-rest-encrypted value (crypto.IsEncrypted): SeedDefaults encrypts every +// value it inserts when a settings encryptor is configured (not only +// Class.Secret keys), so an untouched encrypted row's ciphertext never +// equals the plaintext default computed here regardless of whether an +// operator ever touched it. BackfillSystemSettingOrigin does not attempt +// this comparison for encrypted rows at all -- see its loop -- so this map +// is only ever consulted for plaintext values. +// SEM@2daf3be663df9da54323f16d115f12d78d435c3f: build the key/value map SeedDefaults would insert today, mirroring its dedupe rule (pure) +func expectedSeedValues() map[string]string { + defaults := models.DefaultSystemSettings() + expected := make(map[string]string, len(defaults)) + for _, d := range defaults { + expected[string(d.SettingKey)] = string(d.Value) + } + for _, ms := range config.DefaultOperationalSettings() { + if ms.Value == "" { + continue + } + if _, ok := expected[ms.Key]; ok { + continue + } + expected[ms.Key] = ms.Value + } + return expected +} + +// BackfillSystemSettingOrigin stamps origin = 'explicit' on every +// NULL-origin system_settings row that shows operator intent -- modified_by +// is set, or its value differs from (or is not covered by) what +// expectedSeedValues reports SeedDefaults would seed for that key today. +// Every other NULL-origin row is left as NULL, which now reads as seeded +// (see the package comment). +// +// Known residual gap (oracle-db-admin review, #794, round 2): the +// modified_by signal is also set by ReEncryptAll on every row it +// re-encrypts (api/settings_service.go), which is a mechanical key-rotation +// pass, not an operator changing a value. A pre-#794 install that ever ran +// key rotation therefore has modified_by on every row and this backfill +// stamps all of them explicit, including genuinely seeded rows -- reviving +// the DB-always-wins outage class through the modified_by door rather than +// the encryption door this function otherwise closes. Not fixed here: it +// reproduces exactly the pre-#794 behavior for that narrow case (no new +// failure mode) and the correct fix is in ReEncryptAll itself (it arguably +// should not touch modified_by/modified_at for a mechanical re-encryption at +// all), not in this backfill's predicate. +// +// Idempotent and cheap in steady state: once a row is stamped, it no longer +// matches `origin IS NULL` and is never re-examined. Safe to run on every +// boot, ahead of AutoMigrate, mirroring DeduplicateGroups (group_dedupe.go). +// SEM@2daf3be663df9da54323f16d115f12d78d435c3f: backfill explicit origin onto pre-existing system_settings rows that show operator intent (writes DB) +func BackfillSystemSettingOrigin(db *gorm.DB) (int64, error) { + logger := slogging.Get() + settingsTable := (&models.SystemSetting{}).TableName() + + present, err := requireMigrationTable(db, settingsTable, "system_settings.origin backfill (#794)") + if err != nil { + return 0, err + } + if !present { + return 0, nil + } + + var rows []models.SystemSetting + err = withMigrationRetry("system_settings origin backfill scan", func() error { + rows = rows[:0] + return db.Where("origin IS NULL").Find(&rows).Error + }) + if err != nil { + return 0, fmt.Errorf("failed to load NULL-origin system_settings rows: %w", err) + } + if len(rows) == 0 { + return 0, nil + } + + expected := expectedSeedValues() + + var explicitKeys []string + for _, row := range rows { + key := string(row.SettingKey) + if row.ModifiedBy.Valid { + // An admin API write or dbtool import touched this row before + // Origin existed to record it. + explicitKeys = append(explicitKeys, key) + continue + } + if crypto.IsEncrypted(string(row.Value)) { + // Ciphertext can never equal a plaintext registry default -- see + // expectedSeedValues and the function comment above. Rely on + // modified_by alone for an encrypted row; leave it NULL here. + continue + } + defaultValue, known := expected[key] + if !known || string(row.Value) != defaultValue { + // Either not a registry key SeedDefaults would have inserted at + // all (so something else deliberately created it), or its value + // has moved away from the current registry default. + explicitKeys = append(explicitKeys, key) + } + // Otherwise: value matches the registry default and no modified_by + // is recorded -- leave origin NULL (reads as seeded). + } + if len(explicitKeys) == 0 { + return 0, nil + } + + var totalUpdated int64 + for _, chunk := range chunkStrings(explicitKeys) { + err = withMigrationRetry("system_settings origin backfill update", func() error { + // UpdateColumn, not Update: SystemSetting.ModifiedAt carries + // autoUpdateTime, and a plain Update injects it into the SET + // clause even though this call only names "origin" -- silently + // destroying every touched row's real last-modified timestamp, + // which is operator-visible via the admin config API + // (oracle-db-admin review, #794). UpdateColumn skips both hooks + // and that auto-timestamp injection. + result := db.Model(&models.SystemSetting{}). + Where("setting_key IN ? AND origin IS NULL", chunk). + UpdateColumn("origin", models.SystemSettingOriginExplicit) + if result.Error != nil { + return result.Error + } + totalUpdated += result.RowsAffected + return nil + }) + if err != nil { + return totalUpdated, fmt.Errorf("failed to stamp explicit origin on backfilled system_settings rows: %w", err) + } + } + + if totalUpdated > 0 { + logger.Info("system_settings origin backfill: stamped %d pre-existing row(s) explicit (#794)", totalUpdated) + } + return totalUpdated, nil +} + +// EnsureSystemSettingOriginCheckConstraint adds systemSettingOriginCheckName +// to system_settings, restricting origin to NULL, 'seeded', or 'explicit'. +// Unlike EnsureSparseUserEmailIndex/EnsureUserProviderLookupUnique, the CHECK +// clause itself is dialect-independent -- PostgreSQL and Oracle both accept +// `CHECK (origin IS NULL OR origin IN ('seeded','explicit'))` verbatim -- so +// only the idempotency story differs. Neither engine has an +// `ADD CONSTRAINT IF NOT EXISTS`, so the catalog is probed first, the same +// pattern #732/#736 use for indexes and tables. +// +// A no-op on SQLite: it is a test-fixture-only dialect here (see +// sparseUserEmailIndexExists), and unlike CREATE INDEX, SQLite's ALTER TABLE +// has no ADD CONSTRAINT form at all -- there is nothing to probe or run. +// SEM@2daf3be663df9da54323f16d115f12d78d435c3f: add the origin CHECK constraint to system_settings, idempotently per dialect (writes DB) +func EnsureSystemSettingOriginCheckConstraint(db *gorm.DB) error { + settingsTable := (&models.SystemSetting{}).TableName() + + present, err := requireMigrationTable(db, settingsTable, "system_settings.origin CHECK constraint (#794)") + if err != nil { + return err + } + if !present { + return nil + } + + if db.Name() == "sqlite" { + slogging.Get().Debug( + "skipping %s: SQLite has no ALTER TABLE ADD CONSTRAINT form; this is a test-fixture-only dialect (#794)", + systemSettingOriginCheckName) + return nil + } + + var exists bool + err = withMigrationRetry("system_settings origin check-constraint existence probe", func() error { + var probeErr error + exists, probeErr = systemSettingOriginCheckExists(db, settingsTable) + return probeErr + }) + if err != nil { + return fmt.Errorf("failed to check whether %s exists: %w", systemSettingOriginCheckName, err) + } + if exists { + return nil + } + + var ddl string + if db.Name() == "oracle" { + // Unquoted, so Oracle folds it to upper case regardless; written + // upper case here to match what the catalog probe below looks for, + // the same convention userProviderLookupDDL and + // EnsureSparseUserEmailIndex use. + ddl = fmt.Sprintf( + `ALTER TABLE %s ADD CONSTRAINT %s CHECK (ORIGIN IS NULL OR ORIGIN IN ('seeded','explicit'))`, + strings.ToUpper(settingsTable), strings.ToUpper(systemSettingOriginCheckName)) + } else { + ddl = fmt.Sprintf( + `ALTER TABLE %s ADD CONSTRAINT %s CHECK (origin IS NULL OR origin IN ('seeded','explicit'))`, + settingsTable, systemSettingOriginCheckName) + } + + err = withDDLRetry("system_settings origin check-constraint create", func() error { + return execMigrationDDL(db, ddl) + }) + switch { + case err == nil: + return nil + case db.Name() == "oracle" && strings.Contains(err.Error(), "ORA-02264"): + // ORA-02264 ("name already used by an existing constraint") is + // ambiguous: it is raised both when a concurrent replica won the + // create race for OUR constraint, and when the name collides with an + // unrelated constraint on another table (Oracle constraint names are + // unique per schema, not per table) or a same-named constraint an + // operator has since DISABLEd (which the probe above already read as + // absent). Re-probe rather than assume success, the same ambiguity + // #732/1.8.4-round-3 resolved for ORA-00955 via + // verifySparseIndexEnforced (oracle-db-admin review, #794). + return verifySystemSettingOriginCheckEnforced(db, settingsTable) + case db.Name() == "postgres" && strings.Contains(err.Error(), "already exists"): + // PostgreSQL: `constraint "..." for relation "..." already exists` + // (SQLSTATE 42710) -- the identical concurrent-replica ambiguity. + return verifySystemSettingOriginCheckEnforced(db, settingsTable) + case dberrors.IsRetryable(dberrors.Classify(err)): + // Lock contention or a connection blip that outlived the retry + // budget. Mirrors EnsureSparseUserEmailIndex's warn-and-continue + // policy: a missing defense-in-depth constraint is a smaller outage + // than a server that will not boot, and the next boot retries. + slogging.Get().Error( + "failed to create %s after %d attempts against %s (transient error: %v). "+ + "The origin column's NULL/seeded/explicit invariant is NOT currently enforced at the database level; "+ + "startup is continuing and the next boot will retry", + systemSettingOriginCheckName, ddlMaxAttempts, settingsTable, err) + return nil + default: + return fmt.Errorf("failed to create %s: %w", systemSettingOriginCheckName, err) + } +} + +// verifySystemSettingOriginCheckEnforced re-probes for systemSettingOriginCheckName +// after a create attempt that raised an ambiguous "already exists"-shaped +// error (ORA-02264 or PostgreSQL's constraint-already-exists text). Mirrors +// sparse_user_index.go's verifySparseIndexEnforced: a same-named collision +// (a different table's constraint on Oracle, where names are unique per +// schema) or an operator-disabled constraint produces the identical error as +// a genuine concurrent-replica race, so re-checking is the only way to tell +// "enforced" from "not enforced but silently reported as fine forever." +// Never returns an error itself -- an unenforced invariant is logged, not +// fatal, matching this function's own warn-and-continue policy throughout +// (oracle-db-admin review, #794). +// SEM@2daf3be663df9da54323f16d115f12d78d435c3f: confirm the origin CHECK constraint exists after an ambiguous create/collision error, logging ERROR if not (reads DB) +func verifySystemSettingOriginCheckEnforced(db *gorm.DB, settingsTable string) error { + var nowExists bool + err := withMigrationRetry("system_settings origin check-constraint post-create verification", func() error { + var probeErr error + nowExists, probeErr = systemSettingOriginCheckExists(db, settingsTable) + return probeErr + }) + if err != nil { + // The create itself may well have succeeded; surface the + // verification failure rather than silently assuming either outcome. + return fmt.Errorf("failed to verify %s after an ambiguous create error: %w", systemSettingOriginCheckName, err) + } + if !nowExists { + slogging.Get().Error( + "%s does not exist on %s after an attempted create that reported it as already present -- "+ + "either the name collides with an unrelated constraint (Oracle constraint names are unique "+ + "per schema, not per table), or an operator has since disabled it. "+ + "The origin column's NULL/seeded/explicit invariant is NOT currently enforced at the database "+ + "level; startup is continuing without it (#794)", + systemSettingOriginCheckName, settingsTable) + } + return nil +} + +// systemSettingOriginCheckExists reports whether systemSettingOriginCheckName +// already exists as an enabled/valid CHECK constraint on settingsTable, per +// dialect. Probed by name only (not structurally, unlike #756's index +// probes): unlike idx_users_provider_lookup or the sparse-email index, this +// constraint name is new in #794 -- no deployed database can already hold a +// differently-defined object under it -- so there is no legacy "impostor" +// case to guard against. +// SEM@2daf3be663df9da54323f16d115f12d78d435c3f: probe whether the system_settings origin CHECK constraint already exists, per dialect (reads DB) +func systemSettingOriginCheckExists(db *gorm.DB, settingsTable string) (bool, error) { + var cnt int64 + var err error + switch db.Name() { + case "oracle": + err = db.Raw( + "SELECT COUNT(*) FROM ALL_CONSTRAINTS WHERE CONSTRAINT_NAME = ? AND TABLE_NAME = ? "+ + "AND OWNER = "+oracleCurrentSchema+" AND CONSTRAINT_TYPE = 'C' AND STATUS = 'ENABLED'", + strings.ToUpper(systemSettingOriginCheckName), strings.ToUpper(settingsTable), + ).Scan(&cnt).Error + case "postgres": + err = db.Raw( + "SELECT COUNT(*) FROM information_schema.table_constraints "+ + "WHERE constraint_name = ? AND table_name = ? AND constraint_type = 'CHECK' "+ + "AND constraint_schema = current_schema()", + systemSettingOriginCheckName, settingsTable, + ).Scan(&cnt).Error + default: + // Unsupported dialect other than the sqlite short-circuit in the + // caller: fail closed (assume absent) so behavior falls back to + // attempting the DDL rather than silently skipping enforcement. + slogging.Get().Warn("systemSettingOriginCheckExists: unsupported dialect %q, assuming constraint absent", db.Name()) + return false, nil + } + if err != nil { + return false, err + } + return cnt > 0, nil +} diff --git a/internal/dbschema/system_setting_origin_backfill_test.go b/internal/dbschema/system_setting_origin_backfill_test.go new file mode 100644 index 00000000..e5f2e69e --- /dev/null +++ b/internal/dbschema/system_setting_origin_backfill_test.go @@ -0,0 +1,287 @@ +package dbschema + +import ( + "testing" + "time" + + "github.com/ericfitz/tmi/api/models" + "github.com/google/uuid" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "gorm.io/driver/sqlite" + "gorm.io/gorm" +) + +// newSystemSettingOriginTestDB builds an in-memory SQLite database migrated +// with the real models.SystemSetting model. Unlike most other tests in this +// package, this one uses the real model directly rather than a test-local +// mirror: BackfillSystemSettingOrigin's logic is defined in terms of +// models.DefaultSystemSettings(), models.SystemSettingOriginExplicit, and +// SystemSetting.IsExplicit() themselves, so mirroring the struct would just +// duplicate DBText/NullableDBVarchar's Scan/Value behavior under test. +// SEM@0000000000000000000000000000000000000000: build an in-memory SQLite DB migrated with the real SystemSetting model (pure) +func newSystemSettingOriginTestDB(t *testing.T) *gorm.DB { + t.Helper() + db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) + require.NoError(t, err) + require.NoError(t, db.AutoMigrate(&models.SystemSetting{})) + return db +} + +// getSystemSetting reloads a single row by key, failing the test if it is +// not found. +// SEM@0000000000000000000000000000000000000000: fetch one system_settings row by key for test assertions (reads DB) +func getSystemSetting(t *testing.T, db *gorm.DB, key string) models.SystemSetting { + t.Helper() + var row models.SystemSetting + require.NoError(t, db.Where("setting_key = ?", key).First(&row).Error) + return row +} + +// TestBackfillSystemSettingOrigin_ModifiedByStampsExplicit covers the first +// operator-intent signal: a NULL-origin row with modified_by set (an admin +// API write or dbtool import from before Origin existed) must become +// explicit, even though its value still happens to equal the registry +// default. +// SEM@0000000000000000000000000000000000000000: validate a NULL-origin row with modified_by set is stamped explicit +func TestBackfillSystemSettingOrigin_ModifiedByStampsExplicit(t *testing.T) { + db := newSystemSettingOriginTestDB(t) + modifier := uuid.NewString() + + require.NoError(t, db.Create(&models.SystemSetting{ + SettingKey: "websocket.max_participants", + Value: "10", // matches the registry default (models.DefaultSystemSettings) + SettingType: models.SystemSettingTypeInt, + ModifiedBy: models.NullableDBVarchar{String: modifier, Valid: true}, + ModifiedAt: time.Now(), + }).Error) + + updated, err := BackfillSystemSettingOrigin(db) + require.NoError(t, err) + assert.Equal(t, int64(1), updated) + + row := getSystemSetting(t, db, "websocket.max_participants") + assert.True(t, row.IsExplicit(), "a modified_by row must become explicit regardless of its value") +} + +// TestBackfillSystemSettingOrigin_PreservesModifiedAt pins oracle-db-admin's +// #794 finding: stamping origin must not disturb modified_at. +// SystemSetting.ModifiedAt carries autoUpdateTime, and a plain GORM Update +// call injects every autoUpdateTime field into the SET clause even when it +// isn't named -- silently destroying the row's real last-modified timestamp, +// which is operator-visible via the admin config API. BackfillSystemSettingOrigin +// must use UpdateColumn to avoid this. +// SEM@0000000000000000000000000000000000000000: validate the backfill does not disturb a stamped row's modified_at timestamp +func TestBackfillSystemSettingOrigin_PreservesModifiedAt(t *testing.T) { + db := newSystemSettingOriginTestDB(t) + modifier := uuid.NewString() + original := time.Now().Add(-30 * 24 * time.Hour).Truncate(time.Second) + + require.NoError(t, db.Create(&models.SystemSetting{ + SettingKey: "websocket.max_participants", + Value: "10", + SettingType: models.SystemSettingTypeInt, + ModifiedBy: models.NullableDBVarchar{String: modifier, Valid: true}, + ModifiedAt: original, + }).Error) + + updated, err := BackfillSystemSettingOrigin(db) + require.NoError(t, err) + assert.Equal(t, int64(1), updated) + + row := getSystemSetting(t, db, "websocket.max_participants") + require.True(t, row.IsExplicit()) + assert.True(t, original.Equal(row.ModifiedAt), + "backfill must not rewrite modified_at: want %s, got %s", original, row.ModifiedAt) +} + +// TestBackfillSystemSettingOrigin_EncryptedValueRelinquishesToSeeded covers +// the at-rest-encryption gap oracle-db-admin flagged for #794: an +// ENC:-prefixed value can never be compared against a plaintext registry +// default, so a NULL-origin encrypted row with no modified_by must be left +// alone (reads as seeded) rather than stamped explicit on a meaningless +// ciphertext mismatch. +// SEM@0000000000000000000000000000000000000000: validate an encrypted NULL-origin row with no modified_by stays seeded rather than being stamped explicit on a meaningless comparison +func TestBackfillSystemSettingOrigin_EncryptedValueRelinquishesToSeeded(t *testing.T) { + db := newSystemSettingOriginTestDB(t) + + require.NoError(t, db.Create(&models.SystemSetting{ + SettingKey: "session.timeout_minutes", // registry default is "60" + Value: "ENC:v1:1:1:notarealciphertextblob", + SettingType: models.SystemSettingTypeInt, + ModifiedAt: time.Now(), + }).Error) + + updated, err := BackfillSystemSettingOrigin(db) + require.NoError(t, err) + assert.Equal(t, int64(0), updated, "an encrypted value must not be compared against the plaintext default") + + row := getSystemSetting(t, db, "session.timeout_minutes") + assert.False(t, row.Origin.Valid) + assert.False(t, row.IsExplicit()) +} + +// TestBackfillSystemSettingOrigin_ValueDiffersStampsExplicit covers the +// second operator-intent signal: a NULL-origin row whose value no longer +// matches what SeedDefaults would seed for that key today must become +// explicit. +// SEM@0000000000000000000000000000000000000000: validate a NULL-origin row whose value differs from the registry default is stamped explicit +func TestBackfillSystemSettingOrigin_ValueDiffersStampsExplicit(t *testing.T) { + db := newSystemSettingOriginTestDB(t) + + require.NoError(t, db.Create(&models.SystemSetting{ + SettingKey: "session.timeout_minutes", + Value: "9999", // registry default is "60" + SettingType: models.SystemSettingTypeInt, + ModifiedAt: time.Now(), + }).Error) + + updated, err := BackfillSystemSettingOrigin(db) + require.NoError(t, err) + assert.Equal(t, int64(1), updated) + + row := getSystemSetting(t, db, "session.timeout_minutes") + assert.True(t, row.IsExplicit()) +} + +// TestBackfillSystemSettingOrigin_UnknownKeyStampsExplicit covers a +// NULL-origin row whose key SeedDefaults would never have inserted at all +// (e.g. an operator- or extension-created setting): with no registry default +// to compare against, it must be treated as showing operator intent. +// SEM@0000000000000000000000000000000000000000: validate a NULL-origin row for a key absent from the registry is stamped explicit +func TestBackfillSystemSettingOrigin_UnknownKeyStampsExplicit(t *testing.T) { + db := newSystemSettingOriginTestDB(t) + + require.NoError(t, db.Create(&models.SystemSetting{ + SettingKey: "custom.not_in_registry", + Value: "anything", + SettingType: models.SystemSettingTypeString, + ModifiedAt: time.Now(), + }).Error) + + updated, err := BackfillSystemSettingOrigin(db) + require.NoError(t, err) + assert.Equal(t, int64(1), updated) + + row := getSystemSetting(t, db, "custom.not_in_registry") + assert.True(t, row.IsExplicit()) +} + +// TestBackfillSystemSettingOrigin_UntouchedDefaultStaysNil covers the +// negative case: a NULL-origin row that still holds the registry default +// with no modified_by must be left alone, so it continues to read as seeded. +// SEM@0000000000000000000000000000000000000000: validate a NULL-origin row matching the registry default with no modified_by is left untouched +func TestBackfillSystemSettingOrigin_UntouchedDefaultStaysNil(t *testing.T) { + db := newSystemSettingOriginTestDB(t) + + require.NoError(t, db.Create(&models.SystemSetting{ + SettingKey: "rate_limit.requests_per_minute", + Value: "100", // exact registry default + SettingType: models.SystemSettingTypeInt, + ModifiedAt: time.Now(), + }).Error) + + updated, err := BackfillSystemSettingOrigin(db) + require.NoError(t, err) + assert.Equal(t, int64(0), updated) + + row := getSystemSetting(t, db, "rate_limit.requests_per_minute") + assert.False(t, row.Origin.Valid, "an untouched seeded default must stay NULL-origin") + assert.False(t, row.IsExplicit()) +} + +// TestBackfillSystemSettingOrigin_AlreadyStampedRowsUntouched covers rows +// already carrying a non-NULL origin: the backfill must never revisit them, +// even when their value would otherwise look like it "differs" from the +// registry default. +// SEM@0000000000000000000000000000000000000000: validate rows already stamped seeded or explicit are never revisited +func TestBackfillSystemSettingOrigin_AlreadyStampedRowsUntouched(t *testing.T) { + db := newSystemSettingOriginTestDB(t) + + require.NoError(t, db.Create(&models.SystemSetting{ + SettingKey: "ui.default_theme", + Value: "definitely-not-the-default", + SettingType: models.SystemSettingTypeString, + Origin: models.NullableDBVarchar{String: models.SystemSettingOriginSeeded, Valid: true}, + ModifiedAt: time.Now(), + }).Error) + require.NoError(t, db.Create(&models.SystemSetting{ + SettingKey: "upload.max_file_size_mb", + Value: "10", // matches the registry default, but already explicit + SettingType: models.SystemSettingTypeInt, + Origin: models.NullableDBVarchar{String: models.SystemSettingOriginExplicit, Valid: true}, + ModifiedAt: time.Now(), + }).Error) + + updated, err := BackfillSystemSettingOrigin(db) + require.NoError(t, err) + assert.Equal(t, int64(0), updated, "already-stamped rows must not be touched") + + seededRow := getSystemSetting(t, db, "ui.default_theme") + assert.Equal(t, models.SystemSettingOriginSeeded, seededRow.Origin.String) + + explicitRow := getSystemSetting(t, db, "upload.max_file_size_mb") + assert.True(t, explicitRow.IsExplicit()) +} + +// TestBackfillSystemSettingOrigin_Idempotent covers running the backfill +// twice: the second pass must find nothing left to stamp and must not +// change any row the first pass already resolved. +// SEM@0000000000000000000000000000000000000000: validate running the backfill twice produces the same result as running it once +func TestBackfillSystemSettingOrigin_Idempotent(t *testing.T) { + db := newSystemSettingOriginTestDB(t) + modifier := uuid.NewString() + + require.NoError(t, db.Create(&models.SystemSetting{ + SettingKey: "websocket.max_participants", + Value: "10", + SettingType: models.SystemSettingTypeInt, + ModifiedBy: models.NullableDBVarchar{String: modifier, Valid: true}, + ModifiedAt: time.Now(), + }).Error) + require.NoError(t, db.Create(&models.SystemSetting{ + SettingKey: "rate_limit.requests_per_minute", + Value: "100", + SettingType: models.SystemSettingTypeInt, + ModifiedAt: time.Now(), + }).Error) + + firstUpdated, err := BackfillSystemSettingOrigin(db) + require.NoError(t, err) + assert.Equal(t, int64(1), firstUpdated) + + explicitAfterFirst := getSystemSetting(t, db, "websocket.max_participants") + seededAfterFirst := getSystemSetting(t, db, "rate_limit.requests_per_minute") + + secondUpdated, err := BackfillSystemSettingOrigin(db) + require.NoError(t, err) + assert.Equal(t, int64(0), secondUpdated, "a second pass must find nothing left to stamp") + + explicitAfterSecond := getSystemSetting(t, db, "websocket.max_participants") + seededAfterSecond := getSystemSetting(t, db, "rate_limit.requests_per_minute") + assert.Equal(t, explicitAfterFirst.Origin, explicitAfterSecond.Origin) + assert.Equal(t, seededAfterFirst.Origin, seededAfterSecond.Origin) +} + +// TestBackfillSystemSettingOrigin_NoRows covers the empty-table fast path: +// no NULL-origin rows at all must be a cheap no-op, not an error. +// SEM@0000000000000000000000000000000000000000: validate the backfill no-ops cleanly against an empty system_settings table +func TestBackfillSystemSettingOrigin_NoRows(t *testing.T) { + db := newSystemSettingOriginTestDB(t) + + updated, err := BackfillSystemSettingOrigin(db) + require.NoError(t, err) + assert.Equal(t, int64(0), updated) +} + +// TestEnsureSystemSettingOriginCheckConstraint_NoOpOnSQLite documents and +// pins the SQLite short-circuit: SQLite has no ALTER TABLE ADD CONSTRAINT +// form, so the function must return cleanly without attempting any DDL +// against the test-fixture dialect used throughout this package's tests. +// SEM@0000000000000000000000000000000000000000: validate the origin CHECK constraint installer no-ops cleanly on SQLite +func TestEnsureSystemSettingOriginCheckConstraint_NoOpOnSQLite(t *testing.T) { + db := newSystemSettingOriginTestDB(t) + require.NoError(t, EnsureSystemSettingOriginCheckConstraint(db)) + // Calling it again must remain a harmless no-op. + require.NoError(t, EnsureSystemSettingOriginCheckConstraint(db)) +}