Skip to content

M1-47 Feature flags

No Date Ruling Reason
M1-47/1 2026-10-14 The registry in code owns key, kind, default, module and description; FeatureFlagPut has no description and requires reason. feature_flags has no description column, so an orphaned row is listed without a description (the brief's "description from the row" and "description copied from the registry" have no column to read or write). ADR 0064 and 03 feature_flags: one owner of the registration; the API schema marks the description absent for orphaned keys.
M1-47/2 2026-10-14 A registered flag without a row has its default and version = 0; If-Match: "0" matches it, and writing the default with an empty rollout writes nothing, appends no event and answers 200 (audit allow with no_change, as M1-46/3). Same rule as the instance settings (03); a no-op must not raise a version or notify clients.
M1-47/3 2026-10-14 Flag evaluation reaches groups and people through the source ports FlagGroupSource (authz/adapters/flagsource, Membership.InEffect of M1-27) and FlagUserSource (identity/adapters/flagsource), declared by instance. ExistingGroupIDs reads each group by its key (no new authz query), because a rollout is entered by hand and stays short. instance may not depend on authz or identity (02 4.4); the source implementations own the validity rules.
M1-47/4 2026-10-14 No flag cache; errors of the flag store or the group source are returned, never turned into "off". One indexed read per call keeps a change in another process effective at once; a database fault shows as 5xx instead of silently disabling a feature.
M1-47/5 2026-10-14 Admin of instance/app implements instance.Flags and the new port instance.FlagAdmin (embedded in instance.Admin), with AdminDeps.Flags, FlagGroups and FlagUsers required; the use case is PutFlag(ctx, actor, PutFlagCmd) like the other use cases of the port, not PutFlag(ctx, cmd). One wiring point (wire.NewInstanceSettings) and the reuse of instance.updated and the step-up path of M1-46.
M1-47/6 2026-10-14 The allow audit of feature_flag.update carries key, enabled, rollout_user_ids, rollout_group_ids and reason, instead of a nested rollout object. The audit value kinds have lists but no nested objects (kernel/auditing); the content is the same, IDs only.
M1-47/7 2026-10-14 putFeatureFlag checks in this order: authorization, 404 for a key that is not registered (orphaned too), 400 for the body (reason blank or with a forbidden character at /reason; every unknown rollout ID with the index as sent), then under the row lock 412. A concurrent first insert of the same key makes the second writer read the new row: with If-Match it answers 412, without it it writes over the new row. A body error needs no lock; the rollout pointers name what the caller sent, before normalization.
M1-47/8 2026-10-14 wire.IdentityWrites.PersonalTokens and app.PersonalTokensDefault are removed; IdentityWrites.Flags (required) binds PersonalTokenSwitch to identity/adapters/instanceflags. The switch evaluates for the actor of the request when it is the caller, else as a person. The integration tests set the flag through feature_flags instead of a fake switch. Brief scope item 8: no stand-in remains.
M1-47/9 2026-10-14 make flag-check also fails on a registered flag missing from configuration.md 7 and on a row there without registration; the table must list exactly the registered flags. It is repository-wide like register-check and skips only when services/server/go.mod is not tracked. The brief's "differs from the registry in key" in both directions; COMPONENTS selects test runs, not the registry rules.
M1-47/10 2026-10-14 Branch feat/M1-47-feature-flags from int/m1-wave13 (82693e5) in its own worktree; not merged locally and not pushed; the worktree stays for the PR lane. The migration is 00018_feature_flags.sql. Instructions of the launcher, which override the brief's merge step (as M1-46/13).
M1-47/11 2026-10-15 Port onto main: the migration is 00021_feature_flags.sql as in the integration branch (INT-W14/2; M1-47/10 was written with the number 18 of its base), its comments, the code comments, the module READMEs and the gate rows name the English chapter files and headings (new checksum recorded with make migrations-lock). make flag-check reads the default column of the English table "7. Feature flags" of configuration.md with on and off instead of an and aus, and its fixtures are English. main holds M1-38, M1-40 and M1-41 with 00018 to 00020, so 00021 follows without a gap; the configuration reference on main is English, so the German words would make the gate fail on the real table. No other behaviour changes.