Tier 1 feature gap improvements (all 9 features)#6
Merged
Conversation
- from_telemetry/2: fallback clause returns :ignore for unexpected events; attach/2 callback skips :ignore so a future event-list change can't crash - audit_test: on_exit detach guard prevents handler leak on assertion failure - document :by option on enable/2, disable/2, clear/2 - fix from_telemetry arity in test description (/3 -> /2)
- Gate.new(:variant) rejects all-zero weight maps (was silently returning the
alphabetically-last variant); add a regression test
- Migrations.upgrade_v2/0 uses plain add/2 instead of add_if_not_exists/2, which
the SQLite3 adapter rejects ("Not supported by SQLite3"); add a migrator-driven
test that proves the column is added to a v1 table
- fix Bandera.variant/2 docstring (flag name, not actor name); drop dead code in
the schema-v2 column test
- Flag.enabled?/2 actor path: resolve percentage only via check_percentage_gate/3 instead of folding base/1 in, so a percentage_of_time gate is no longer drawn twice (which inflated its effective probability); restores pre-Plan-3 semantics - Constraint :matches compiles patterns leniently (Regex.compile/1) so a malformed stored pattern is a non-match rather than crashing every evaluating caller; add a regression test - document the intentional grant-when-matched (always-enabled) encoding for rule gates in the Redis serializer - clarify that an explicit empty context equals no context
- schedule gate fails closed on a malformed/non-ISO-8601 window bound instead of
crashing every evaluation (DateTime.compare on a non-DateTime); add a test
- stale_flags/1 excludes internal bandera_segment:* keys, which are never
evaluated and so would always look stale; add a test
- mix bandera.flags --stale warns when Bandera.Usage isn't running
- cover the requires: {parent, false} (parent-must-be-disabled) path
- Bandera.Usage tracks [:bandera, :variant] as well as [:bandera, :enabled?] so flags resolved only via variant/2 are not misreported as stale; add a test - mix bandera.flags listing also hides internal bandera_segment:* flags, matching stale_flags/1 - fix README upgrade_v2/0 description to match the plain add/2 implementation
- variant weight distribution (deterministic) + zero-weight never chosen - schedule gate boundaries: open-ended, from-only, until-only - multi-level prerequisite chain and prerequisite x context-rule interaction - :default fallback through the :context path on store error - rule and schedule gates end-to-end through the Ecto value column - Bandera.Usage records via a real enabled?/2 call
…e reasons to use Bandera - reframe Why Bandera? around the three advanced capabilities (multivariate flags, data-defined targeting, scheduling), keeping runtime config and async testing as the fundamentals - expand the intro to mention variants, rules/segments, prerequisites, scheduling - add Prerequisites and Scheduling sections (previously only in the feature guide) - note schedule gates in the evaluation-precedence summary - link the new Feature Guide and the other guides from the docs section
- coerce numeric strings before eq/neq/in/not_in and ordering, so a string context value (the common JSON/params case) is compared by value, not by Elixir term ordering. Fixes silent gate inversion: "5" no longer passes >= 18, a stringified banned id is still caught by :not_in, etc. Number/non-numeric-string ordering now fails closed. - memoize compiled :matches regexes in :persistent_term instead of recompiling on every evaluation; document that :matches is unanchored Addresses adversarial findings #1 (type confusion) and #6 (regex recompile).
A negative weight passed the previous "at least one positive" guard and made the total <= 0, which silently routed 100% of actors to the alphabetically-last variant. Validate that every weight is a non-negative number. Addresses adversarial finding #3.
enable(when: []) and put_segment(name, []) now raise ArgumentError, and Flag.enabled? ignores a rule gate with no constraints (Enum.all?([]) is vacuously true, which previously granted to every context). Addresses adversarial finding #9.
…nown parents
- memoize each flag's resolved state within a single enabled?/2 call, so a shared
(diamond) prerequisite graph is evaluated once instead of O(2^depth) re-walks
- propagate cycles as a distinct :cycle status so they fail closed uniformly,
including required:false edges (which a plain false previously satisfied, making
a mutual requires:{_,false} cycle report both flags enabled)
- treat a non-atom (unresolved) parent name as not-met (fail closed)
Addresses adversarial findings #5, #7, and the prerequisite half of #4.
- a corrupt or foreign gate (bad JSON, unknown gate_type/field, malformed ratio) is now dropped with a warning instead of raising, so one bad row no longer takes down deserialize_flag / all_flags / the dashboard - prerequisite parent names resolve via String.to_existing_atom (kept as a string, which fails closed, when the atom is unknown) so corrupt store data can't exhaust the atom table Addresses adversarial findings #2, #L7, and the serializer half of #4.
Telemetry auto-detaches a handler that raises, which would silently stop the audit log (a compliance gap) or usage tracking. Catch and log exceptions from the user's audit callback, and guard the usage handler, so a transient failure no longer kills the hook. Addresses adversarial finding #8.
- stale_flags/1 clamps :older_than to >= 0 so a negative value can't push the cutoff
into the future and report every flag (incl. fresh ones) as stale (#L2)
- document the new enable/2 scopes and clarify that conditional scopes (when/
for_segment/requires/schedule) return {:ok, true} on a successful write and are
evaluated per call (#L9)
3 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements all nine Tier 1 features from the feature-gap analysis, test-first, across the four sequenced plans in
docs/superpowers/plans/2026-05-21-tier1-0{1..4}-*.md. No breaking changes — existing gates serialize withvalue: niland behave exactly as before.Plan 1 — Quick Wins
:defaultfallback onenabled?/2(fail-open on store errors)Bandera.Auditlog:Eventstruct +attach/2/detach/1over existing write telemetry, with a:byidentity option threaded intoenable/disable/clearPlan 2 — Schema v2 + Multivariate (foundation for 3 & 4)
valuecolumn (JSON via Jason) +upgrade_v2/0migration helper:variantgates with stable per-actor bucketing;Bandera.variant/2+put_variants/3Plan 3 — Data-Defined Targeting
Bandera.Constraintwith 10 operators + map round-trip:rulegates (constraint list invalue), context-awareFlag.enabled?/2,enable(when:),:contextoptionput_segment/2,:segmentgates expanded at evaluation time in the impure layer (Flagstays pure)Plan 4 — Prerequisites, Scheduling & Tooling
:prerequisitegates viarequires:with cycle detection:schedulegates with pure UTC time-window evaluation (active in both actor and non-actor paths)Bandera.UsageETS/telemetry tracker,stale_flags/1,mix bandera.flags --staleEach plan went through spec-compliance and code-quality review plus a final cross-plan review; fixes are committed (notably: variant all-zero-weight guard, SQLite-safe
upgrade_v2/0, percentage-of-time double-draw fix, lenient regex/schedule parsing that fails closed, andUsagetrackingvariant/2evaluations).Test Plan
mix test— 339 passed (81 doctests, 8 properties, 250 tests), 13 Redis integration tests excluded (no local Redis)mix format --check-formatted— cleanmix credo --strict— no issuesmix assay(incremental Dialyzer) — 0 errors