Skip to content

M1-46 Instance settings, branding and discovery

No Date Ruling Reason
M1-46/1 2026-10-13 Branding keys change only through updateBranding (and the logo operations of M3-10); putInstanceSetting rejects them with 400 validation_failed, field #path/key, code use_branding_endpoint. One action per write path and one audit action per change.
M1-46/2 2026-10-13 The branding version and its ETag are the sum of the row versions of branding.product_name, branding.accent_color and branding.logo_file_id (absent row 0). An absent accent_color in updateBranding writes the default spruce as a value; no writer deletes a settings row (bothy_app has no DELETE). Every write raises one of the three versions by one, so the sum rises strictly and serves as the optimistic lock (06 section 8 item 1); a deleted row would lower it.
M1-46/3 2026-10-13 A registered key without a row has its registry default, version = 0 and neither updated_by nor updated_at; If-Match: "0" matches it. Writing the value it already has (also the default of a key without a row) writes nothing, appends no event and answers 200 with the current setting; the audit entry carries no_change. The stored row wins (03 instance_settings); a no-op must not raise a version or tell clients that something changed (05 section 6). audit: always of the action catalogue still records the call.
M1-46/4 2026-10-13 The accent keys are the explicit list of ADR 0044 in kernel/designkeys/accent.go, not derived from the tokens; a test holds every accent among the generated category colours. granite is the neutral palette of all surfaces, and a new category colour becomes an accent only after the contrast gate (ADR 0044).
M1-46/5 2026-10-13 kernel/designkeys is a kernel-core package (02 4.5). chat (M1-52, M1-53) needs the same keys and may not import instance (02 4.4).
M1-46/6 2026-10-13 local_password_enabled in discovery is true when auth.local_password_enabled allows it and an enabled provider of kind local exists. The password with TOTP needs a local account provider (08 section 2.6); offering it without one would show a login that cannot succeed.
M1-46/7 2026-10-13 listInstanceSettings omits a registered key that has neither a row nor a default (branding.logo_file_id before a logo is set). InstanceSetting.value is required and the API never sends null (ADR 0053); the key has no value to report.
M1-46/8 2026-10-13 A platform with only client.recommended_version.<p> set appears in min_client_versions with min_version: "0.0.0". The contract requires min_version, and 0.0.0 admits every app version, which is what an unset minimum means.
M1-46/9 2026-10-13 updateBranding reports both field errors (/product_name, /accent_color) of one request together; a concurrent first write of a branding key answers 412 version_conflict with the branding read after the rollback. One round trip shows every problem of the form; the concurrent writer committed a newer branding, so the caller's version is outdated.
M1-46/10 2026-10-13 The instance settings are built in their own start step settings between authz and identity (wire.NewInstanceSettings); identity gets the read port instance.Settings through IdentityWrites.Settings, and discovery the sign-in ways through identity/adapters/loginoptions over the provider store. createUser reads locale.default (PLAN row M1-46), so the settings must exist before identity; the provider store needs no use case of identity, so the source has no start order of its own.
M1-46/11 2026-10-13 The integration tests of the six operations live in identity/adapters/httpapi beside the session world of M1-34 and M1-35. They need the real authentication and step-up, whose helpers are test code of that package; the settings are wired with the same wire function as the server.
M1-46/12 2026-10-13 tools/ci/tests/test_product_name_places.py allows the working name in code and configuration only in the forms that 02 6 now lists: module path, whole path segment (lower case), prefix (bothy-, bothy_, bothy., BOTHY_), alert names Bothy<Name> defined in the alert rules (and the examples of the alert tooling), binary before a subcommand (in cmd/bothy also the command word and identifiers), a whole value outside the client code (apps/, packages/), display names in the tool files of 02 6, and the fixed advisory lock keys. It skips docs/, Markdown, lock files and code generated from docs/. The few prose uses in comments, alert summaries and Terraform descriptions were reworded, and the SQL dollar-quote tag in platform/jobs/migrate.go became $schema$. The kinds of 02 6 did not cover the tool and operations ids, the default database name and the tool display names the repository has; the implementation proved the list incomplete (brief scope item 1), and these forms keep a change of the name a mechanical replace.
M1-46/13 2026-10-13 Branch feat/M1-46-instance-settings from int/m1-wave11 (2064556) in its own worktree; the branch is not merged locally and not pushed, and the worktree stays for the PR lane. The migration is 00014_instance_settings.sql. Instructions of the launcher, which override the brief's merge step (as M1-36/1).
M1-46/14 2026-10-13 Every writer of instance_settings first takes the transaction-scoped advisory lock hashtext('bothy.instance_settings') (LockSettings), then locks the rows of its keys. Row locks alone let two updateBranding calls with the same If-Match change different keys without a row and both win (red team M1-46, reproduced by TestUpdateBranding_ConcurrentWritersOneWins); settings writes are rare owner actions, so one lock for all keys costs nothing.
M1-46/15 2026-10-15 Port onto main: the migration keeps the number 00017 of the integration branch, its comments name the English chapter files (new checksum recorded with make migrations-lock), and the code comments, the configuration test and tools/ci/tests/test_product_name_places.py name the English chapter files and section titles (02 "6. Code layout", "Allowed dependencies", the display names in the tools), and TestSettingsRegistryMatchesConfigurationDoc reads the English section "6. Instance settings" with empty for no default; the integration fixes INT-W13/3 to INT-W13/6 are part of this commit, and the Compose, collector and Terraform texts the ticket reworded say "server" in English; the comment of the portability job in .github/workflows/release.yml says "the product component" instead of the bare working name. main already holds M1-37 (00016), M1-44 and M1-50, as the integration branch did, so the number follows without a gap; the specification is English on main; the portability job came to main with a later port and is not on the integration branch, so the check of the working name's places found its comment only here. No behaviour changes.