Module: Moult::Flags::Classification
- Defined in:
- lib/moult/flags/classification.rb
Overview
The per-finding model for feature flags — this slice's realisation of Moult's
protected per-finding API. Like a packwerk boundary violation (see
Boundaries::Severity), a flag reference is a recorded FACT, not a
probabilistic candidate: the scanner saw the call site. So we never manufacture
a fake confidence (the finding's confidence is null); the per-finding signal
is a categorical CLASSIFICATION instead — the flag's value_type, how many times
it is referenced, and the literal default value(s) observed.
The genuinely confidence-graded judgement — staleness (is this flag dead / obsolete?) — needs a live OpenFeature provider to know which keys still exist, and is deferred (like the Coverband/Flipper live stores). So the humility invariant holds in this register too: a static scan can never prove a flag is unused (it may be referenced dynamically, via provider config, or from outside the codebase), and nothing here says it is.
Classification.classify is a pure function of the observed signals — no IO, no Prism nodes — so it is pinned against hand-built inputs exactly like ABC, the coverage Resolver, the duplication Confidence model, and Boundaries::Severity. Drift is a bug.
Defined Under Namespace
Classes: Assessment, Reason
Constant Summary collapse
- CATEGORY =
"feature_flag"- VALUE_TYPES =
The value-type classification. boolean/string/number/object are read from the fetch_
_* method; unknownis reserved for a flag referenced with more than one type (an ambiguity we record rather than resolve). %w[boolean string number object unknown].freeze
- MIXED =
"unknown"
Class Method Summary collapse
- .classify(value_types:, default_values:) ⇒ Assessment
- .pluralize(count, noun) ⇒ Object
- .type_reason(value_type, observed, reference_count) ⇒ Object
Class Method Details
.classify(value_types:, default_values:) ⇒ Assessment
53 54 55 56 57 58 59 60 61 62 63 64 |
# File 'lib/moult/flags/classification.rb', line 53 def classify(value_types:, default_values:) observed = value_types.uniq.sort value_type = (observed.size == 1) ? observed.first : MIXED reference_count = value_types.size defaults = default_values.compact.uniq.sort reasons = [type_reason(value_type, observed, reference_count)] reasons << Reason.new(rule: :reference_count, detail: "referenced at #{pluralize(reference_count, "call site")}") reasons << Reason.new(rule: :default_values, detail: "observed default value(s): #{defaults.join(", ")}") unless defaults.empty? Assessment.new(value_type: value_type, reference_count: reference_count, default_values: defaults, reasons: reasons) end |
.pluralize(count, noun) ⇒ Object
74 75 76 |
# File 'lib/moult/flags/classification.rb', line 74 def pluralize(count, noun) "#{count} #{noun}#{"s" unless count == 1}" end |
.type_reason(value_type, observed, reference_count) ⇒ Object
66 67 68 69 70 71 72 |
# File 'lib/moult/flags/classification.rb', line 66 def type_reason(value_type, observed, reference_count) if value_type == MIXED Reason.new(rule: :mixed_value_types, detail: "referenced with differing value types (#{observed.join(", ")}); the flag type is ambiguous") else Reason.new(rule: :"#{value_type}_flag", detail: "evaluated as a #{value_type} flag across #{pluralize(reference_count, "reference")}") end end |