| 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 2.2 |
|
| 69 |
1.1 2.2 |
|
| 70 |
1.1 |
|
| 72 |
1.1 2.2 3.3 |
|
| 81 |
1.1 |