TagAiDrift.java

1
package org.egothor.methodatlas;
2
3
import java.util.List;
4
import java.util.Locale;
5
6
import org.egothor.methodatlas.ai.AiMethodSuggestion;
7
8
/**
9
 * Describes the agreement between a source-level {@code @Tag("security")} annotation
10
 * and the AI classification of the same test method.
11
 *
12
 * <p>Both the static annotation and the AI judgment are independent sources of truth.
13
 * When they disagree the discrepancy is called <em>drift</em>:</p>
14
 *
15
 * <ul>
16
 *   <li>{@link #TAG_ONLY} — the method carries {@code @Tag("security")} in source but
17
 *       the AI considers it non-security-relevant. The annotation may be stale,
18
 *       inaccurate, or applied to the wrong method. Tag-based CI gates and audit
19
 *       dashboards over-count security coverage.</li>
20
 *   <li>{@link #AI_ONLY} — the AI classifies the method as security-relevant but no
21
 *       {@code @Tag("security")} is present in source. Coverage dashboards and
22
 *       tag-based CI gates silently miss this test.</li>
23
 *   <li>{@link #NONE} — both sources agree; no action needed.</li>
24
 * </ul>
25
 *
26
 * <p>Drift detection requires an active AI classification. When no
27
 * {@link AiMethodSuggestion} is available (AI disabled, class too large, etc.)
28
 * {@link #compute} returns {@code null}.</p>
29
 *
30
 * @see MethodAtlasApp
31
 * @see org.egothor.methodatlas.emit.OutputEmitter
32
 * @see org.egothor.methodatlas.emit.SarifEmitter
33
 */
34
public enum TagAiDrift {
35
36
    /** Both sources agree — either both say security-relevant or neither does. */
37
    NONE,
38
39
    /**
40
     * Source code carries {@code @Tag("security")} but the AI disagrees.
41
     * The annotation may be stale, inaccurate, or applied to the wrong method.
42
     */
43
    TAG_ONLY,
44
45
    /**
46
     * AI classifies the method as security-relevant but no {@code @Tag("security")}
47
     * is present in source. Coverage dashboards and tag-based CI gates will miss it.
48
     */
49
    AI_ONLY;
50
51
    private static final String SECURITY_TAG_VALUE = "security";
52
53
    /**
54
     * Computes the drift between source-level security tags and the AI classification.
55
     *
56
     * @param sourceTags JUnit {@code @Tag} values extracted from the method
57
     * @param suggestion AI classification for the method, or {@code null} when AI
58
     *                   is disabled or unavailable
59
     * @return computed drift value, or {@code null} when {@code suggestion} is
60
     *         {@code null} (drift cannot be determined without AI classification)
61
     */
62
    public static TagAiDrift compute(List<String> sourceTags, AiMethodSuggestion suggestion) {
63 2 1. compute : removed conditional - replaced equality check with true → KILLED
2. compute : removed conditional - replaced equality check with false → KILLED
        if (suggestion == null) {
64
            return null;
65
        }
66
        boolean hasTag = sourceTags.stream()
67
                .anyMatch(SECURITY_TAG_VALUE::equalsIgnoreCase);
68
        boolean aiSaysSecure = suggestion.securityRelevant();
69 2 1. compute : removed conditional - replaced equality check with false → KILLED
2. compute : removed conditional - replaced equality check with true → KILLED
        if (hasTag == aiSaysSecure) {
70 1 1. compute : replaced return value with null for org/egothor/methodatlas/TagAiDrift::compute → KILLED
            return NONE;
71
        }
72 3 1. compute : removed conditional - replaced equality check with false → KILLED
2. compute : replaced return value with null for org/egothor/methodatlas/TagAiDrift::compute → KILLED
3. compute : removed conditional - replaced equality check with true → KILLED
        return hasTag ? TAG_ONLY : AI_ONLY;
73
    }
74
75
    /**
76
     * Returns the lowercase hyphenated string representation used in CSV and SARIF output.
77
     *
78
     * @return {@code "none"}, {@code "tag-only"}, or {@code "ai-only"}
79
     */
80
    public String toValue() {
81 1 1. toValue : replaced return value with "" for org/egothor/methodatlas/TagAiDrift::toValue → KILLED
        return name().toLowerCase(Locale.ROOT).replace('_', '-');
82
    }
83
}

Mutations

63

1.1
Location : compute
Killed by : org.egothor.methodatlas.TagAiDriftTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.TagAiDriftTest]/[method:compute_noTagAndAiNonSecurity_returnsNone()]
removed conditional - replaced equality check with true → KILLED

2.2
Location : compute
Killed by : org.egothor.methodatlas.TagAiDriftTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.TagAiDriftTest]/[method:compute_nullSuggestion_returnsNull()]
removed conditional - replaced equality check with false → KILLED

69

1.1
Location : compute
Killed by : org.egothor.methodatlas.TagAiDriftTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.TagAiDriftTest]/[method:compute_noTagAndAiNonSecurity_returnsNone()]
removed conditional - replaced equality check with false → KILLED

2.2
Location : compute
Killed by : org.egothor.methodatlas.TagAiDriftTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.TagAiDriftTest]/[method:compute_nonSecurityTagAndAiSecure_returnsAiOnly()]
removed conditional - replaced equality check with true → KILLED

70

1.1
Location : compute
Killed by : org.egothor.methodatlas.TagAiDriftTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.TagAiDriftTest]/[method:compute_neitherSecurityRelevant_returnsNone()]
replaced return value with null for org/egothor/methodatlas/TagAiDrift::compute → KILLED

72

1.1
Location : compute
Killed by : org.egothor.methodatlas.TagAiDriftTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.TagAiDriftTest]/[method:compute_tagPresentAiDisagrees_returnsTagOnly()]
removed conditional - replaced equality check with false → KILLED

2.2
Location : compute
Killed by : org.egothor.methodatlas.TagAiDriftTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.TagAiDriftTest]/[method:compute_nonSecurityTagAndAiSecure_returnsAiOnly()]
replaced return value with null for org/egothor/methodatlas/TagAiDrift::compute → KILLED

3.3
Location : compute
Killed by : org.egothor.methodatlas.TagAiDriftTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.TagAiDriftTest]/[method:compute_nonSecurityTagAndAiSecure_returnsAiOnly()]
removed conditional - replaced equality check with true → KILLED

81

1.1
Location : toValue
Killed by : org.egothor.methodatlas.TagAiDriftTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.TagAiDriftTest]/[method:toValue_returnsExpectedStrings()]
replaced return value with "" for org/egothor/methodatlas/TagAiDrift::toValue → KILLED

Active mutators

Tests examined


Report generated by PIT 1.22.1