AttestCollector.java

1
// SPDX-License-Identifier: Apache-2.0
2
// Copyright 2026 Egothor
3
// Copyright 2026 Accenture
4
package org.egothor.methodatlas.attest;
5
6
import java.time.Instant;
7
import java.util.ArrayList;
8
import java.util.Collections;
9
import java.util.List;
10
import java.util.concurrent.CopyOnWriteArrayList;
11
import java.util.concurrent.atomic.AtomicInteger;
12
13
import org.egothor.methodatlas.ai.AiMethodSuggestion;
14
import org.egothor.methodatlas.emit.TestMethodSink;
15
16
/**
17
 * Streaming sink that builds an {@link AttestReport} from scan records observed
18
 * during a single run.
19
 *
20
 * <p>
21
 * The collector implements {@link TestMethodSink} so the orchestration layer
22
 * can hand it the same record stream that drives CSV/SARIF emitters, without a
23
 * second scan and without coupling the emitters to attestation logic.
24
 * </p>
25
 *
26
 * <p>
27
 * Only AI-classified, security-relevant methods whose confidence meets the
28
 * configured threshold are included in the manifest. All other methods are
29
 * counted toward {@code totalMethods} but not listed.
30
 * </p>
31
 *
32
 * <p>
33
 * Package-private because nothing outside the {@code attest} package needs to
34
 * construct one; {@link AttestFacade} is the sole external entry point.
35
 * </p>
36
 */
37
final class AttestCollector implements TestMethodSink {
38
39
    /** Schema version of the produced manifest. */
40
    private static final String SCHEMA_VERSION = "1";
41
42
    /** Minimum AI confidence required to include a method in the manifest. */
43
    private final double minConfidence;
44
45
    /** Scan root paths forwarded to the report header. */
46
    private final List<String> scanRoots;
47
48
    /** Running count of all methods seen (security-relevant or not). */
49
    private final AtomicInteger totalSeen = new AtomicInteger();
50
51
    /** Accumulated security-relevant entries in encounter order. Thread-safe for parallel-AI mode. */
52
    private final List<AttestEntry> entries = new CopyOnWriteArrayList<>();
53
54
    /**
55
     * Creates a collector for the attestation manifest.
56
     *
57
     * @param minConfidence minimum AI confidence threshold; values below this are
58
     *                      not included in the manifest
59
     * @param scanRoots     scan root paths shown in the report header
60
     */
61
    /* default */ AttestCollector(double minConfidence, List<String> scanRoots) {
62
        this.minConfidence = minConfidence;
63
        this.scanRoots = List.copyOf(scanRoots);
64
    }
65
66
    /**
67
     * Records a test method for potential inclusion in the attestation manifest.
68
     *
69
     * @param fqcn        fully qualified class name
70
     * @param method      test method name
71
     * @param beginLine   ignored
72
     * @param loc         ignored
73
     * @param contentHash SHA-256 fingerprint of the enclosing class source, or
74
     *                    {@code null} when {@code -content-hash} was not used
75
     * @param tags        ignored (attestation relies on AI classification only)
76
     * @param displayName ignored
77
     * @param suggestion  optional AI classification
78
     */
79
    @Override
80
    @SuppressWarnings("PMD.UseObjectForClearerAPI")
81
    public void record(String fqcn, String method, int beginLine, int loc, String contentHash,
82
            List<String> tags, String displayName, AiMethodSuggestion suggestion) {
83
        totalSeen.incrementAndGet();
84 4 1. record : removed conditional - replaced equality check with false → NO_COVERAGE
2. record : removed conditional - replaced equality check with true → NO_COVERAGE
3. record : removed conditional - replaced equality check with false → NO_COVERAGE
4. record : removed conditional - replaced equality check with true → NO_COVERAGE
        if (suggestion == null || !suggestion.securityRelevant()) {
85
            return;
86
        }
87 3 1. record : removed conditional - replaced comparison check with true → NO_COVERAGE
2. record : changed conditional boundary → NO_COVERAGE
3. record : removed conditional - replaced comparison check with false → NO_COVERAGE
        if (suggestion.confidence() < minConfidence) {
88
            return;
89
        }
90 2 1. record : removed conditional - replaced equality check with false → NO_COVERAGE
2. record : removed conditional - replaced equality check with true → NO_COVERAGE
        List<String> aiTags = suggestion.tags() != null
91
                ? List.copyOf(suggestion.tags()) : List.of();
92
        entries.add(new AttestEntry(fqcn, method, contentHash, aiTags));
93
    }
94
95
    /**
96
     * Builds the final {@link AttestReport}.
97
     *
98
     * @param toolVersion resolved tool version string; never {@code null}
99
     * @return populated attestation manifest
100
     */
101
    /* default */ AttestReport buildReport(String toolVersion) {
102
        String commitSha = CommitShaResolver.resolve();
103
        List<AttestEntry> snapshot = Collections.unmodifiableList(new ArrayList<>(entries));
104 1 1. buildReport : replaced return value with null for org/egothor/methodatlas/attest/AttestCollector::buildReport → NO_COVERAGE
        return new AttestReport(
105
                SCHEMA_VERSION,
106
                Instant.now().toString(),
107
                toolVersion,
108
                commitSha,
109
                scanRoots,
110
                totalSeen.get(),
111
                snapshot.size(),
112
                snapshot);
113
    }
114
}

Mutations

84

1.1
Location : record
Killed by : none
removed conditional - replaced equality check with false → NO_COVERAGE

2.2
Location : record
Killed by : none
removed conditional - replaced equality check with true → NO_COVERAGE

3.3
Location : record
Killed by : none
removed conditional - replaced equality check with false → NO_COVERAGE

4.4
Location : record
Killed by : none
removed conditional - replaced equality check with true → NO_COVERAGE

87

1.1
Location : record
Killed by : none
removed conditional - replaced comparison check with true → NO_COVERAGE

2.2
Location : record
Killed by : none
changed conditional boundary → NO_COVERAGE

3.3
Location : record
Killed by : none
removed conditional - replaced comparison check with false → NO_COVERAGE

90

1.1
Location : record
Killed by : none
removed conditional - replaced equality check with false → NO_COVERAGE

2.2
Location : record
Killed by : none
removed conditional - replaced equality check with true → NO_COVERAGE

104

1.1
Location : buildReport
Killed by : none
replaced return value with null for org/egothor/methodatlas/attest/AttestCollector::buildReport → NO_COVERAGE

Active mutators

Tests examined


Report generated by PIT 1.30.1 support