EvidencePackCommand.java

1
// SPDX-License-Identifier: Apache-2.0
2
// Copyright 2026 Egothor
3
// Copyright 2026 Accenture
4
package org.egothor.methodatlas.evidence;
5
6
import java.io.IOException;
7
import java.io.OutputStreamWriter;
8
import java.io.PrintWriter;
9
import java.nio.charset.StandardCharsets;
10
import java.nio.file.Files;
11
import java.nio.file.Path;
12
import java.nio.file.Paths;
13
import java.nio.file.StandardCopyOption;
14
import java.security.GeneralSecurityException;
15
import java.time.Instant;
16
import java.util.LinkedHashMap;
17
import java.util.List;
18
import java.util.Map;
19
import java.util.logging.Level;
20
import java.util.logging.Logger;
21
22
import tools.jackson.databind.SerializationFeature;
23
import tools.jackson.databind.json.JsonMapper;
24
25
import org.egothor.methodatlas.AiResultCache;
26
import org.egothor.methodatlas.CliConfig;
27
import org.egothor.methodatlas.ai.AiSuggestionEngine;
28
import org.egothor.methodatlas.api.TestDiscoveryConfig;
29
import org.egothor.methodatlas.command.ContentHasher;
30
import org.egothor.methodatlas.command.ScanOrchestrator;
31
import org.egothor.methodatlas.emit.ClassificationOverride;
32
import org.egothor.methodatlas.emit.CompositeTestMethodSink;
33
import org.egothor.methodatlas.emit.OutputEmitter;
34
import org.egothor.methodatlas.emit.OutputMode;
35
import org.egothor.methodatlas.emit.SarifEmitter;
36
import org.egothor.methodatlas.emit.TestMethodSink;
37
38
/**
39
 * Materialises a tamper-evident evidence pack on disk by running one scan and
40
 * bundling every artefact an auditor needs to verify it later.
41
 *
42
 * <p>
43
 * The command is selected from {@code MethodAtlasApp} when the user passes
44
 * {@code -evidence-pack <framework>}. It owns its output directory: it
45
 * creates the directory if absent, refuses to overwrite an existing one
46
 * unless {@code -evidence-pack-overwrite} was supplied, writes all
47
 * artefacts, computes a SHA-256 manifest, and optionally signs that manifest
48
 * via ZeroEcho.
49
 * </p>
50
 *
51
 * <p>
52
 * {@code MethodAtlasApp} is the only caller; the type is {@code public} so the
53
 * root package can construct it and read its {@link #outputDir()} and
54
 * {@link #framework()} for the post-run summary.
55
 * </p>
56
 *
57
 * @since 4.0.0
58
 */
59
public final class EvidencePackCommand {
60
61
    private static final Logger LOG = Logger.getLogger(EvidencePackCommand.class.getName());
62
63
    /** Exit code returned for success. */
64
    private static final int EXIT_OK = 0;
65
66
    /** Exit code returned when one or more files produced a scan error. */
67
    private static final int EXIT_SCAN_ERROR = 1;
68
69
    /** Subdirectory used when no explicit -evidence-pack-dir is supplied. */
70
    private static final String DEFAULT_PARENT = "evidence-packs";
71
72
    /** Manifest filename inside the pack. */
73
    private static final String MANIFEST_FILE = "manifest.sha256";
74
75
    /** Signed-envelope filename inside the pack. */
76
    private static final String MANIFEST_SIGNED_FILE = "manifest.sha256.signed";
77
78
    /** Filename of the SARIF artefact. */
79
    private static final String SARIF_FILE = "findings.sarif";
80
81
    /** Filename of the CSV artefact. */
82
    private static final String CSV_FILE = "findings.csv";
83
84
    /** Filename of the copied override file. */
85
    private static final String OVERRIDES_FILE = "overrides.yaml";
86
87
    /** Filename of the AI provenance file. */
88
    private static final String AI_RESPONSES_FILE = "ai-responses.jsonl";
89
90
    /** Filename of the pack metadata file. */
91
    private static final String META_FILE = "pack-meta.json";
92
93
    private final CliConfig cliConfig;
94
    private final EvidencePackOptions packOptions;
95
    private final TestDiscoveryConfig discoveryConfig;
96
    private final AiSuggestionEngine aiEngine;
97
    private final ClassificationOverride override;
98
    private final AiResultCache aiCache;
99
    private final ScanOrchestrator orchestrator;
100
101
    /**
102
     * Creates a new evidence-pack command.
103
     *
104
     * @param cliConfig       parsed CLI configuration
105
     * @param packOptions     evidence-pack–specific options
106
     * @param discoveryConfig discovery configuration forwarded to providers
107
     * @param aiEngine        AI engine, or {@code null} when AI is disabled
108
     * @param override        classification override
109
     * @param aiCache         AI result cache
110
     * @param orchestrator    pre-built scan orchestrator
111
     */
112
    public EvidencePackCommand(CliConfig cliConfig, EvidencePackOptions packOptions,
113
            TestDiscoveryConfig discoveryConfig, AiSuggestionEngine aiEngine,
114
            ClassificationOverride override, AiResultCache aiCache,
115
            ScanOrchestrator orchestrator) {
116
        this.cliConfig = cliConfig;
117
        this.packOptions = packOptions;
118
        this.discoveryConfig = discoveryConfig;
119
        this.aiEngine = aiEngine;
120
        this.override = override;
121
        this.aiCache = aiCache;
122
        this.orchestrator = orchestrator;
123
    }
124
125
    /**
126
     * Executes the command: runs the scan, writes every pack artefact, and —
127
     * when a keyring is configured — signs the manifest.
128
     *
129
     * <p>
130
     * Ordering matters for the integrity chain. Artefacts are written first,
131
     * then {@code pack-meta.json} (optimistically recording whether signing was
132
     * requested), then {@code manifest.sha256} (the SHA-256 of every artefact,
133
     * including {@code pack-meta.json}), and finally the manifest is signed. If
134
     * signing fails, {@code pack-meta.json} is rewritten as unsigned, any partial
135
     * {@code manifest.sha256.signed} is deleted, and {@code manifest.sha256} is
136
     * re-hashed so its {@code pack-meta.json} digest still matches the file on
137
     * disk. A signing failure is non-fatal — the pack is produced unsigned.
138
     * </p>
139
     *
140
     * @return {@code 0} on success, {@code 1} when one or more source files
141
     *         produced a parse or processing error
142
     * @throws IOException if any pack artefact cannot be written
143
     */
144
    public int execute() throws IOException {
145
        Path outputDir = resolveOutputDir();
146 1 1. execute : removed call to org/egothor/methodatlas/evidence/EvidencePackCommand::prepareOutputDir → KILLED
        prepareOutputDir(outputDir);
147 2 1. execute : removed conditional - replaced equality check with false → SURVIVED
2. execute : removed conditional - replaced equality check with true → TIMED_OUT
        List<Path> roots = cliConfig.paths().isEmpty() ? List.of(Paths.get(".")) : cliConfig.paths();
148
149
        AiResponseArchive aiArchive = wireAiArchive();
150
        ScanResult scanResult = runScan(outputDir, roots);
151
152 1 1. execute : removed call to org/egothor/methodatlas/evidence/EvidencePackCommand::copyOverridesIfPresent → SURVIVED
        copyOverridesIfPresent(outputDir);
153 1 1. execute : removed call to org/egothor/methodatlas/evidence/AiResponseArchive::flush → SURVIVED
        aiArchive.flush(outputDir.resolve(AI_RESPONSES_FILE));
154
155
        SignResult signResult = signIfRequested(outputDir);
156 1 1. execute : removed call to org/egothor/methodatlas/evidence/EvidencePackCommand::writePackMeta → KILLED
        writePackMeta(outputDir, roots, signResult);
157 1 1. execute : removed call to org/egothor/methodatlas/evidence/ManifestWriter::write → KILLED
        ManifestWriter.write(outputDir, outputDir.resolve(MANIFEST_FILE));
158
159 4 1. execute : removed conditional - replaced equality check with true → SURVIVED
2. execute : removed conditional - replaced equality check with true → KILLED
3. execute : removed conditional - replaced equality check with false → KILLED
4. execute : removed conditional - replaced equality check with false → KILLED
        if (signResult != null && signResult.signer != null) {
160
            // try-with-resources releases the signer's owned context (the hybrid
161
            // SignatureContext) once signing completes or fails.
162
            try (ZeroEchoSigner signer = signResult.signer) {
163
                signer.sign(
164
                        outputDir.resolve(MANIFEST_FILE),
165
                        outputDir.resolve(MANIFEST_SIGNED_FILE));
166
            } catch (IOException | GeneralSecurityException e) {
167
                if (LOG.isLoggable(Level.WARNING)) {
168
                    LOG.log(Level.WARNING, "Manifest signing failed", e);
169
                }
170
                signResult.signFailed = true;
171
                // pack-meta.json was written with signed=true before the manifest
172
                // was hashed; now that signing failed it is rewritten as
173
                // signed=false. Discard any partial signature envelope and
174
                // re-hash the manifest so its pack-meta.json digest matches the
175
                // corrected file — otherwise an auditor would see a false tamper
176
                // mismatch on an (intentionally) unsigned pack.
177 1 1. execute : removed call to org/egothor/methodatlas/evidence/EvidencePackCommand::writePackMeta → KILLED
                writePackMeta(outputDir, roots, signResult);
178
                Files.deleteIfExists(outputDir.resolve(MANIFEST_SIGNED_FILE));
179 1 1. execute : removed call to org/egothor/methodatlas/evidence/ManifestWriter::write → KILLED
                ManifestWriter.write(outputDir, outputDir.resolve(MANIFEST_FILE));
180
            }
181
        }
182
183 2 1. execute : removed conditional - replaced equality check with false → SURVIVED
2. execute : removed conditional - replaced equality check with true → KILLED
        return scanResult.hadErrors ? EXIT_SCAN_ERROR : EXIT_OK;
184
    }
185
186
    /**
187
     * Returns the absolute path of the produced pack directory. Useful for
188
     * the caller's success message.
189
     *
190
     * @return resolved absolute output directory
191
     */
192
    public Path outputDir() {
193 1 1. outputDir : replaced return value with null for org/egothor/methodatlas/evidence/EvidencePackCommand::outputDir → KILLED
        return resolveOutputDir().toAbsolutePath();
194
    }
195
196
    /**
197
     * Returns the resolved framework name (canonical token).
198
     *
199
     * @return canonical framework token
200
     */
201
    public String framework() {
202 1 1. framework : replaced return value with "" for org/egothor/methodatlas/evidence/EvidencePackCommand::framework → NO_COVERAGE
        return packOptions.framework().canonicalToken();
203
    }
204
205
    // -------------------------------------------------------------------------
206
    // Internal steps
207
    // -------------------------------------------------------------------------
208
209
    private Path resolveOutputDir() {
210 2 1. resolveOutputDir : removed conditional - replaced equality check with true → SURVIVED
2. resolveOutputDir : removed conditional - replaced equality check with false → KILLED
        if (packOptions.outputDir() != null) {
211 1 1. resolveOutputDir : replaced return value with null for org/egothor/methodatlas/evidence/EvidencePackCommand::resolveOutputDir → KILLED
            return packOptions.outputDir();
212
        }
213 2 1. resolveOutputDir : removed conditional - replaced equality check with true → NO_COVERAGE
2. resolveOutputDir : removed conditional - replaced equality check with false → NO_COVERAGE
        Path base = cliConfig.paths().isEmpty() ? Paths.get(".") : cliConfig.paths().get(0);
214 1 1. resolveOutputDir : replaced return value with null for org/egothor/methodatlas/evidence/EvidencePackCommand::resolveOutputDir → NO_COVERAGE
        return base.resolve(DEFAULT_PARENT).resolve(packOptions.framework().canonicalToken());
215
    }
216
217
    private void prepareOutputDir(Path dir) throws IOException {
218 2 1. prepareOutputDir : removed conditional - replaced equality check with false → KILLED
2. prepareOutputDir : removed conditional - replaced equality check with true → KILLED
        if (Files.exists(dir)) {
219 2 1. prepareOutputDir : removed conditional - replaced equality check with false → KILLED
2. prepareOutputDir : removed conditional - replaced equality check with true → KILLED
            if (!packOptions.overwrite()) {
220
                throw new IOException("Evidence pack directory already exists (use "
221
                        + "-evidence-pack-overwrite to allow reuse): " + dir);
222
            }
223 2 1. prepareOutputDir : removed conditional - replaced equality check with false → SURVIVED
2. prepareOutputDir : removed conditional - replaced equality check with true → KILLED
            if (!Files.isDirectory(dir)) {
224
                throw new IOException("Evidence pack path is not a directory: " + dir);
225
            }
226
        } else {
227
            Files.createDirectories(dir);
228
        }
229
    }
230
231
    private AiResponseArchive wireAiArchive() {
232
        AiResponseArchive archive = new AiResponseArchive();
233 2 1. wireAiArchive : removed conditional - replaced equality check with false → SURVIVED
2. wireAiArchive : removed conditional - replaced equality check with true → KILLED
        if (aiEngine != null) {
234 1 1. wireAiArchive : removed call to org/egothor/methodatlas/ai/AiSuggestionEngine::setResponseListener → NO_COVERAGE
            aiEngine.setResponseListener(archive);
235
        }
236 1 1. wireAiArchive : replaced return value with null for org/egothor/methodatlas/evidence/EvidencePackCommand::wireAiArchive → KILLED
        return archive;
237
    }
238
239
    private ScanResult runScan(Path outputDir, List<Path> roots) throws IOException {
240 2 1. runScan : removed conditional - replaced equality check with false → SURVIVED
2. runScan : removed conditional - replaced equality check with true → SURVIVED
        boolean aiEnabled = aiEngine != null;
241 4 1. runScan : removed conditional - replaced equality check with false → SURVIVED
2. runScan : removed conditional - replaced equality check with true → SURVIVED
3. runScan : removed conditional - replaced equality check with true → NO_COVERAGE
4. runScan : removed conditional - replaced equality check with false → NO_COVERAGE
        boolean confidenceEnabled = aiEnabled && cliConfig.aiOptions().confidence();
242
        String filePrefix = ContentHasher.filePrefix(roots);
243
244
        SarifEmitter sarifEmitter = new SarifEmitter(aiEnabled, confidenceEnabled, filePrefix,
245 2 1. runScan : removed conditional - replaced equality check with false → SURVIVED
2. runScan : removed conditional - replaced equality check with true → SURVIVED
                !cliConfig.sarifOmitScores());
246
247
        try (PrintWriter csvWriter = new PrintWriter(
248
                new OutputStreamWriter(
249
                        Files.newOutputStream(outputDir.resolve(CSV_FILE)),
250
                        StandardCharsets.UTF_8), true)) {
251
            OutputEmitter csvEmitter = new OutputEmitter(csvWriter, aiEnabled, confidenceEnabled,
252
                    cliConfig.contentHash(), cliConfig.driftDetect(), false);
253 1 1. runScan : removed call to org/egothor/methodatlas/emit/OutputEmitter::emitCsvHeader → SURVIVED
            csvEmitter.emitCsvHeader(OutputMode.CSV);
254
255
            TestMethodSink csvSink = (fqcn, method, beginLine, loc, contentHash, tags,
256
                    displayName, suggestion) ->
257 1 1. lambda$runScan$0 : removed call to org/egothor/methodatlas/emit/OutputEmitter::emit → SURVIVED
                csvEmitter.emit(OutputMode.CSV, fqcn, method, loc, contentHash, tags,
258
                        displayName, suggestion, null);
259
260
            TestMethodSink composite = new CompositeTestMethodSink(sarifEmitter, csvSink);
261
            TestMethodSink filtered = orchestrator.filterSink(composite, cliConfig.securityOnly(),
262
                    cliConfig.minConfidence(), confidenceEnabled);
263
264
            int result = orchestrator.scan(roots, cliConfig, discoveryConfig, aiEngine,
265
                    filtered, override, aiCache);
266
267
            // Surface any write error swallowed by the streaming CSV writer before
268
            // the manifest is hashed, so a truncated manifest.csv cannot be signed.
269 1 1. runScan : removed call to org/egothor/methodatlas/emit/OutputEmitter::finish → SURVIVED
            csvEmitter.finish();
270
271 1 1. runScan : removed call to org/egothor/methodatlas/evidence/EvidencePackCommand::writeSarif → KILLED
            writeSarif(outputDir, sarifEmitter);
272 3 1. runScan : removed conditional - replaced equality check with false → SURVIVED
2. runScan : replaced return value with null for org/egothor/methodatlas/evidence/EvidencePackCommand::runScan → KILLED
3. runScan : removed conditional - replaced equality check with true → KILLED
            return new ScanResult(result != 0);
273
        }
274
    }
275
276
    private static void writeSarif(Path outputDir, SarifEmitter sarifEmitter) throws IOException {
277
        try (PrintWriter sarifWriter = new PrintWriter(
278
                new OutputStreamWriter(
279
                        Files.newOutputStream(outputDir.resolve(SARIF_FILE)),
280
                        StandardCharsets.UTF_8), true)) {
281 1 1. writeSarif : removed call to org/egothor/methodatlas/emit/SarifEmitter::flush → SURVIVED
            sarifEmitter.flush(sarifWriter);
282
        }
283
    }
284
285
    private void copyOverridesIfPresent(Path outputDir) throws IOException {
286
        Path overrideFile = cliConfig.overrideFile();
287 4 1. copyOverridesIfPresent : removed conditional - replaced equality check with false → NO_COVERAGE
2. copyOverridesIfPresent : removed conditional - replaced equality check with false → SURVIVED
3. copyOverridesIfPresent : removed conditional - replaced equality check with true → NO_COVERAGE
4. copyOverridesIfPresent : removed conditional - replaced equality check with true → KILLED
        if (overrideFile != null && Files.exists(overrideFile)) {
288
            Files.copy(overrideFile, outputDir.resolve(OVERRIDES_FILE),
289
                    StandardCopyOption.REPLACE_EXISTING);
290
        }
291
    }
292
293
    private SignResult signIfRequested(Path outputDir) {
294
        // A secret environment variable (CI/CD) takes precedence over a keyring
295
        // file (interactive CLI); when neither is configured the pack is unsigned.
296 2 1. signIfRequested : removed conditional - replaced equality check with false → SURVIVED
2. signIfRequested : removed conditional - replaced equality check with true → KILLED
        if (packOptions.keyringEnv() != null) {
297 1 1. signIfRequested : replaced return value with null for org/egothor/methodatlas/evidence/EvidencePackCommand::signIfRequested → NO_COVERAGE
            return signFromEnv(outputDir, packOptions.keyringEnv());
298
        }
299 2 1. signIfRequested : removed conditional - replaced equality check with false → KILLED
2. signIfRequested : removed conditional - replaced equality check with true → KILLED
        if (packOptions.keyringFile() != null) {
300 1 1. signIfRequested : replaced return value with null for org/egothor/methodatlas/evidence/EvidencePackCommand::signIfRequested → KILLED
            return signFromFile(outputDir, packOptions.keyringFile());
301
        }
302 1 1. signIfRequested : replaced return value with null for org/egothor/methodatlas/evidence/EvidencePackCommand::signIfRequested → SURVIVED
        return new SignResult(null, false);
303
    }
304
305
    private SignResult signFromEnv(Path outputDir, String envVar) {
306
        String keyringText = System.getenv(envVar);
307 4 1. signFromEnv : removed conditional - replaced equality check with false → NO_COVERAGE
2. signFromEnv : removed conditional - replaced equality check with true → NO_COVERAGE
3. signFromEnv : removed conditional - replaced equality check with true → NO_COVERAGE
4. signFromEnv : removed conditional - replaced equality check with false → NO_COVERAGE
        if (keyringText == null || keyringText.isBlank()) {
308
            if (LOG.isLoggable(Level.WARNING)) {
309
                LOG.log(Level.WARNING, "Keyring environment variable {0} is unset or empty; pack will be unsigned",
310
                        envVar);
311
            }
312 1 1. signFromEnv : replaced return value with null for org/egothor/methodatlas/evidence/EvidencePackCommand::signFromEnv → NO_COVERAGE
            return new SignResult(null, true);
313
        }
314
        try {
315
            ZeroEchoSigner signer = ZeroEchoSigner.fromKeyringText(keyringText,
316
                    packOptions.keyAlias(), packOptions.signatureAlgorithm());
317 1 1. signFromEnv : replaced return value with null for org/egothor/methodatlas/evidence/EvidencePackCommand::signFromEnv → NO_COVERAGE
            return new SignResult(signer, false);
318
        } catch (IOException | GeneralSecurityException e) {
319
            if (LOG.isLoggable(Level.WARNING)) {
320
                LOG.log(Level.WARNING, "Failed to initialise ZeroEcho signer from " + envVar + " for " + outputDir, e);
321
            }
322 1 1. signFromEnv : replaced return value with null for org/egothor/methodatlas/evidence/EvidencePackCommand::signFromEnv → NO_COVERAGE
            return new SignResult(null, true);
323
        }
324
    }
325
326
    private SignResult signFromFile(Path outputDir, Path keyringFile) {
327
        try {
328
            ZeroEchoSigner signer = ZeroEchoSigner.fromKeyringFile(keyringFile,
329
                    packOptions.keyAlias(), packOptions.signatureAlgorithm());
330 1 1. signFromFile : replaced return value with null for org/egothor/methodatlas/evidence/EvidencePackCommand::signFromFile → KILLED
            return new SignResult(signer, false);
331
        } catch (IOException | GeneralSecurityException e) {
332
            if (LOG.isLoggable(Level.WARNING)) {
333
                LOG.log(Level.WARNING, "Failed to initialise ZeroEcho signer for " + outputDir, e);
334
            }
335 1 1. signFromFile : replaced return value with null for org/egothor/methodatlas/evidence/EvidencePackCommand::signFromFile → NO_COVERAGE
            return new SignResult(null, true);
336
        }
337
    }
338
339
    private void writePackMeta(Path outputDir, List<Path> roots, SignResult signResult)
340
            throws IOException {
341
        Map<String, Object> meta = new LinkedHashMap<>();
342
        meta.put("framework", packOptions.framework().canonicalToken());
343
        meta.put("methodAtlasVersion", versionString());
344
        meta.put("javaVersion", System.getProperty("java.version"));
345
        meta.put("os", System.getProperty("os.name") + " " + System.getProperty("os.version"));
346 1 1. lambda$writePackMeta$1 : replaced return value with "" for org/egothor/methodatlas/evidence/EvidencePackCommand::lambda$writePackMeta$1 → SURVIVED
        meta.put("scanRoots", roots.stream().map(p -> p.toAbsolutePath().toString()).toList());
347
        meta.put("generatedUtc", Instant.now().toString());
348
349 6 1. writePackMeta : removed conditional - replaced equality check with true → SURVIVED
2. writePackMeta : removed conditional - replaced equality check with false → KILLED
3. writePackMeta : removed conditional - replaced equality check with true → KILLED
4. writePackMeta : removed conditional - replaced equality check with false → KILLED
5. writePackMeta : removed conditional - replaced equality check with true → KILLED
6. writePackMeta : removed conditional - replaced equality check with false → KILLED
        boolean signed = signResult != null && signResult.signer != null && !signResult.signFailed;
350
        meta.put("signed", signed);
351 2 1. writePackMeta : removed conditional - replaced equality check with true → KILLED
2. writePackMeta : removed conditional - replaced equality check with false → KILLED
        meta.put("signatureAlgorithm", signed ? signResult.signer.algorithm() : null);
352 2 1. writePackMeta : removed conditional - replaced equality check with false → KILLED
2. writePackMeta : removed conditional - replaced equality check with true → KILLED
        meta.put("zeroEchoLibVersion", signed ? ZeroEchoSigner.ZEROECHO_LIB_VERSION : null);
353 2 1. writePackMeta : removed conditional - replaced equality check with true → KILLED
2. writePackMeta : removed conditional - replaced equality check with false → KILLED
        meta.put("keyAlias", signed ? signResult.signer.resolvedAlias() : null);
354
355
        JsonMapper mapper = JsonMapper.builder()
356
                .enable(SerializationFeature.INDENT_OUTPUT)
357
                .build();
358
        Files.writeString(outputDir.resolve(META_FILE),
359
                mapper.writeValueAsString(meta), StandardCharsets.UTF_8);
360
    }
361
362
    private static String versionString() {
363
        String impl = EvidencePackCommand.class.getPackage().getImplementationVersion();
364 3 1. versionString : removed conditional - replaced equality check with true → SURVIVED
2. versionString : replaced return value with "" for org/egothor/methodatlas/evidence/EvidencePackCommand::versionString → SURVIVED
3. versionString : removed conditional - replaced equality check with false → SURVIVED
        return impl != null ? impl : "dev";
365
    }
366
367
    // -------------------------------------------------------------------------
368
    // Internal types
369
    // -------------------------------------------------------------------------
370
371
    /** Outcome of the scan step. */
372
    private static final class ScanResult {
373
        /** {@code true} when at least one provider reported errors. */
374
        private final boolean hadErrors;
375
376
        /* default */ ScanResult(boolean hadErrors) {
377
            this.hadErrors = hadErrors;
378
        }
379
    }
380
381
    /** Outcome of the optional signing step. */
382
    private static final class SignResult {
383
        /** Configured signer, or {@code null} when signing was skipped. */
384
        private final ZeroEchoSigner signer;
385
386
        /** Set to {@code true} when signing was attempted but failed. */
387
        private boolean signFailed;
388
389
        /* default */ SignResult(ZeroEchoSigner signer, boolean signFailed) {
390
            this.signer = signer;
391
            this.signFailed = signFailed;
392
        }
393
    }
394
}

Mutations

146

1.1
Location : execute
Killed by : org.egothor.methodatlas.EvidencePackCommandTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.EvidencePackCommandTest]/[method:existingDirectoryWithoutOverwriteFlagIsAnError(java.nio.file.Path)]
removed call to org/egothor/methodatlas/evidence/EvidencePackCommand::prepareOutputDir → KILLED

147

1.1
Location : execute
Killed by : none
removed conditional - replaced equality check with false → SURVIVED
Covering tests

2.2
Location : execute
Killed by : none
removed conditional - replaced equality check with true → TIMED_OUT

152

1.1
Location : execute
Killed by : none
removed call to org/egothor/methodatlas/evidence/EvidencePackCommand::copyOverridesIfPresent → SURVIVED
Covering tests

153

1.1
Location : execute
Killed by : none
removed call to org/egothor/methodatlas/evidence/AiResponseArchive::flush → SURVIVED
Covering tests

156

1.1
Location : execute
Killed by : org.egothor.methodatlas.EvidencePackCommandTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.EvidencePackCommandTest]/[method:caseInsensitiveFrameworkTokenIsAccepted(java.nio.file.Path)]
removed call to org/egothor/methodatlas/evidence/EvidencePackCommand::writePackMeta → KILLED

157

1.1
Location : execute
Killed by : org.egothor.methodatlas.EvidencePackCommandTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.EvidencePackCommandTest]/[method:manifestHasOneLinePerArtefactWith64HexDigest(java.nio.file.Path)]
removed call to org/egothor/methodatlas/evidence/ManifestWriter::write → KILLED

159

1.1
Location : execute
Killed by : org.egothor.methodatlas.EvidencePackCommandTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.EvidencePackCommandTest]/[method:caseInsensitiveFrameworkTokenIsAccepted(java.nio.file.Path)]
removed conditional - replaced equality check with true → KILLED

2.2
Location : execute
Killed by : org.egothor.methodatlas.EvidencePackCommandTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.EvidencePackCommandTest]/[method:signsManifestWithGeneratedEd25519Keyring(java.nio.file.Path)]
removed conditional - replaced equality check with false → KILLED

3.3
Location : execute
Killed by : org.egothor.methodatlas.EvidencePackCommandTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.EvidencePackCommandTest]/[method:signsManifestWithGeneratedEd25519Keyring(java.nio.file.Path)]
removed conditional - replaced equality check with false → KILLED

4.4
Location : execute
Killed by : none
removed conditional - replaced equality check with true → SURVIVED
Covering tests

177

1.1
Location : execute
Killed by : org.egothor.methodatlas.EvidencePackCommandTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.EvidencePackCommandTest]/[method:signingFailureLeavesAConsistentUnsignedPack(java.nio.file.Path)]
removed call to org/egothor/methodatlas/evidence/EvidencePackCommand::writePackMeta → KILLED

179

1.1
Location : execute
Killed by : org.egothor.methodatlas.EvidencePackCommandTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.EvidencePackCommandTest]/[method:signingFailureLeavesAConsistentUnsignedPack(java.nio.file.Path)]
removed call to org/egothor/methodatlas/evidence/ManifestWriter::write → KILLED

183

1.1
Location : execute
Killed by : none
removed conditional - replaced equality check with false → SURVIVED
Covering tests

2.2
Location : execute
Killed by : org.egothor.methodatlas.EvidencePackCommandTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.EvidencePackCommandTest]/[method:caseInsensitiveFrameworkTokenIsAccepted(java.nio.file.Path)]
removed conditional - replaced equality check with true → KILLED

193

1.1
Location : outputDir
Killed by : org.egothor.methodatlas.EvidencePackCommandTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.EvidencePackCommandTest]/[method:caseInsensitiveFrameworkTokenIsAccepted(java.nio.file.Path)]
replaced return value with null for org/egothor/methodatlas/evidence/EvidencePackCommand::outputDir → KILLED

202

1.1
Location : framework
Killed by : none
replaced return value with "" for org/egothor/methodatlas/evidence/EvidencePackCommand::framework → NO_COVERAGE

210

1.1
Location : resolveOutputDir
Killed by : none
removed conditional - replaced equality check with true → SURVIVED
Covering tests

2.2
Location : resolveOutputDir
Killed by : org.egothor.methodatlas.EvidencePackCommandTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.EvidencePackCommandTest]/[method:existingDirectoryWithoutOverwriteFlagIsAnError(java.nio.file.Path)]
removed conditional - replaced equality check with false → KILLED

211

1.1
Location : resolveOutputDir
Killed by : org.egothor.methodatlas.EvidencePackCommandTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.EvidencePackCommandTest]/[method:existingDirectoryWithoutOverwriteFlagIsAnError(java.nio.file.Path)]
replaced return value with null for org/egothor/methodatlas/evidence/EvidencePackCommand::resolveOutputDir → KILLED

213

1.1
Location : resolveOutputDir
Killed by : none
removed conditional - replaced equality check with true → NO_COVERAGE

2.2
Location : resolveOutputDir
Killed by : none
removed conditional - replaced equality check with false → NO_COVERAGE

214

1.1
Location : resolveOutputDir
Killed by : none
replaced return value with null for org/egothor/methodatlas/evidence/EvidencePackCommand::resolveOutputDir → NO_COVERAGE

218

1.1
Location : prepareOutputDir
Killed by : org.egothor.methodatlas.EvidencePackCommandTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.EvidencePackCommandTest]/[method:existingDirectoryWithoutOverwriteFlagIsAnError(java.nio.file.Path)]
removed conditional - replaced equality check with false → KILLED

2.2
Location : prepareOutputDir
Killed by : org.egothor.methodatlas.EvidencePackCommandTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.EvidencePackCommandTest]/[method:caseInsensitiveFrameworkTokenIsAccepted(java.nio.file.Path)]
removed conditional - replaced equality check with true → KILLED

219

1.1
Location : prepareOutputDir
Killed by : org.egothor.methodatlas.EvidencePackCommandTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.EvidencePackCommandTest]/[method:existingDirectoryWithoutOverwriteFlagIsAnError(java.nio.file.Path)]
removed conditional - replaced equality check with false → KILLED

2.2
Location : prepareOutputDir
Killed by : org.egothor.methodatlas.EvidencePackCommandTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.EvidencePackCommandTest]/[method:overwriteFlagAllowsReuseOfExistingDirectory(java.nio.file.Path)]
removed conditional - replaced equality check with true → KILLED

223

1.1
Location : prepareOutputDir
Killed by : none
removed conditional - replaced equality check with false → SURVIVED
Covering tests

2.2
Location : prepareOutputDir
Killed by : org.egothor.methodatlas.EvidencePackCommandTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.EvidencePackCommandTest]/[method:overwriteFlagAllowsReuseOfExistingDirectory(java.nio.file.Path)]
removed conditional - replaced equality check with true → KILLED

233

1.1
Location : wireAiArchive
Killed by : none
removed conditional - replaced equality check with false → SURVIVED
Covering tests

2.2
Location : wireAiArchive
Killed by : org.egothor.methodatlas.EvidencePackCommandTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.EvidencePackCommandTest]/[method:caseInsensitiveFrameworkTokenIsAccepted(java.nio.file.Path)]
removed conditional - replaced equality check with true → KILLED

234

1.1
Location : wireAiArchive
Killed by : none
removed call to org/egothor/methodatlas/ai/AiSuggestionEngine::setResponseListener → NO_COVERAGE

236

1.1
Location : wireAiArchive
Killed by : org.egothor.methodatlas.EvidencePackCommandTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.EvidencePackCommandTest]/[method:caseInsensitiveFrameworkTokenIsAccepted(java.nio.file.Path)]
replaced return value with null for org/egothor/methodatlas/evidence/EvidencePackCommand::wireAiArchive → KILLED

240

1.1
Location : runScan
Killed by : none
removed conditional - replaced equality check with false → SURVIVED
Covering tests

2.2
Location : runScan
Killed by : none
removed conditional - replaced equality check with true → SURVIVED Covering tests

241

1.1
Location : runScan
Killed by : none
removed conditional - replaced equality check with false → SURVIVED
Covering tests

2.2
Location : runScan
Killed by : none
removed conditional - replaced equality check with true → SURVIVED Covering tests

3.3
Location : runScan
Killed by : none
removed conditional - replaced equality check with true → NO_COVERAGE

4.4
Location : runScan
Killed by : none
removed conditional - replaced equality check with false → NO_COVERAGE

245

1.1
Location : runScan
Killed by : none
removed conditional - replaced equality check with false → SURVIVED
Covering tests

2.2
Location : runScan
Killed by : none
removed conditional - replaced equality check with true → SURVIVED Covering tests

253

1.1
Location : runScan
Killed by : none
removed call to org/egothor/methodatlas/emit/OutputEmitter::emitCsvHeader → SURVIVED
Covering tests

257

1.1
Location : lambda$runScan$0
Killed by : none
removed call to org/egothor/methodatlas/emit/OutputEmitter::emit → SURVIVED
Covering tests

269

1.1
Location : runScan
Killed by : none
removed call to org/egothor/methodatlas/emit/OutputEmitter::finish → SURVIVED
Covering tests

271

1.1
Location : runScan
Killed by : org.egothor.methodatlas.EvidencePackCommandTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.EvidencePackCommandTest]/[method:manifestHasOneLinePerArtefactWith64HexDigest(java.nio.file.Path)]
removed call to org/egothor/methodatlas/evidence/EvidencePackCommand::writeSarif → KILLED

272

1.1
Location : runScan
Killed by : org.egothor.methodatlas.EvidencePackCommandTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.EvidencePackCommandTest]/[method:caseInsensitiveFrameworkTokenIsAccepted(java.nio.file.Path)]
replaced return value with null for org/egothor/methodatlas/evidence/EvidencePackCommand::runScan → KILLED

2.2
Location : runScan
Killed by : org.egothor.methodatlas.EvidencePackCommandTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.EvidencePackCommandTest]/[method:caseInsensitiveFrameworkTokenIsAccepted(java.nio.file.Path)]
removed conditional - replaced equality check with true → KILLED

3.3
Location : runScan
Killed by : none
removed conditional - replaced equality check with false → SURVIVED
Covering tests

281

1.1
Location : writeSarif
Killed by : none
removed call to org/egothor/methodatlas/emit/SarifEmitter::flush → SURVIVED
Covering tests

287

1.1
Location : copyOverridesIfPresent
Killed by : org.egothor.methodatlas.EvidencePackCommandTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.EvidencePackCommandTest]/[method:caseInsensitiveFrameworkTokenIsAccepted(java.nio.file.Path)]
removed conditional - replaced equality check with true → KILLED

2.2
Location : copyOverridesIfPresent
Killed by : none
removed conditional - replaced equality check with false → NO_COVERAGE

3.3
Location : copyOverridesIfPresent
Killed by : none
removed conditional - replaced equality check with false → SURVIVED
Covering tests

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

296

1.1
Location : signIfRequested
Killed by : none
removed conditional - replaced equality check with false → SURVIVED
Covering tests

2.2
Location : signIfRequested
Killed by : org.egothor.methodatlas.EvidencePackCommandTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.EvidencePackCommandTest]/[method:caseInsensitiveFrameworkTokenIsAccepted(java.nio.file.Path)]
removed conditional - replaced equality check with true → KILLED

297

1.1
Location : signIfRequested
Killed by : none
replaced return value with null for org/egothor/methodatlas/evidence/EvidencePackCommand::signIfRequested → NO_COVERAGE

299

1.1
Location : signIfRequested
Killed by : org.egothor.methodatlas.EvidencePackCommandTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.EvidencePackCommandTest]/[method:signsManifestWithGeneratedEd25519Keyring(java.nio.file.Path)]
removed conditional - replaced equality check with false → KILLED

2.2
Location : signIfRequested
Killed by : org.egothor.methodatlas.EvidencePackCommandTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.EvidencePackCommandTest]/[method:caseInsensitiveFrameworkTokenIsAccepted(java.nio.file.Path)]
removed conditional - replaced equality check with true → KILLED

300

1.1
Location : signIfRequested
Killed by : org.egothor.methodatlas.EvidencePackCommandTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.EvidencePackCommandTest]/[method:signsManifestWithGeneratedEd25519Keyring(java.nio.file.Path)]
replaced return value with null for org/egothor/methodatlas/evidence/EvidencePackCommand::signIfRequested → KILLED

302

1.1
Location : signIfRequested
Killed by : none
replaced return value with null for org/egothor/methodatlas/evidence/EvidencePackCommand::signIfRequested → SURVIVED
Covering tests

307

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

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

3.3
Location : signFromEnv
Killed by : none
removed conditional - replaced equality check with true → NO_COVERAGE

4.4
Location : signFromEnv
Killed by : none
removed conditional - replaced equality check with false → NO_COVERAGE

312

1.1
Location : signFromEnv
Killed by : none
replaced return value with null for org/egothor/methodatlas/evidence/EvidencePackCommand::signFromEnv → NO_COVERAGE

317

1.1
Location : signFromEnv
Killed by : none
replaced return value with null for org/egothor/methodatlas/evidence/EvidencePackCommand::signFromEnv → NO_COVERAGE

322

1.1
Location : signFromEnv
Killed by : none
replaced return value with null for org/egothor/methodatlas/evidence/EvidencePackCommand::signFromEnv → NO_COVERAGE

330

1.1
Location : signFromFile
Killed by : org.egothor.methodatlas.EvidencePackCommandTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.EvidencePackCommandTest]/[method:signsManifestWithGeneratedEd25519Keyring(java.nio.file.Path)]
replaced return value with null for org/egothor/methodatlas/evidence/EvidencePackCommand::signFromFile → KILLED

335

1.1
Location : signFromFile
Killed by : none
replaced return value with null for org/egothor/methodatlas/evidence/EvidencePackCommand::signFromFile → NO_COVERAGE

346

1.1
Location : lambda$writePackMeta$1
Killed by : none
replaced return value with "" for org/egothor/methodatlas/evidence/EvidencePackCommand::lambda$writePackMeta$1 → SURVIVED
Covering tests

349

1.1
Location : writePackMeta
Killed by : org.egothor.methodatlas.EvidencePackCommandTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.EvidencePackCommandTest]/[method:signsManifestWithGeneratedEd25519Keyring(java.nio.file.Path)]
removed conditional - replaced equality check with false → KILLED

2.2
Location : writePackMeta
Killed by : org.egothor.methodatlas.EvidencePackCommandTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.EvidencePackCommandTest]/[method:caseInsensitiveFrameworkTokenIsAccepted(java.nio.file.Path)]
removed conditional - replaced equality check with true → KILLED

3.3
Location : writePackMeta
Killed by : org.egothor.methodatlas.EvidencePackCommandTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.EvidencePackCommandTest]/[method:signsManifestWithGeneratedEd25519Keyring(java.nio.file.Path)]
removed conditional - replaced equality check with false → KILLED

4.4
Location : writePackMeta
Killed by : org.egothor.methodatlas.EvidencePackCommandTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.EvidencePackCommandTest]/[method:signingFailureLeavesAConsistentUnsignedPack(java.nio.file.Path)]
removed conditional - replaced equality check with true → KILLED

5.5
Location : writePackMeta
Killed by : org.egothor.methodatlas.EvidencePackCommandTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.EvidencePackCommandTest]/[method:signsManifestWithGeneratedEd25519Keyring(java.nio.file.Path)]
removed conditional - replaced equality check with false → KILLED

6.6
Location : writePackMeta
Killed by : none
removed conditional - replaced equality check with true → SURVIVED
Covering tests

351

1.1
Location : writePackMeta
Killed by : org.egothor.methodatlas.EvidencePackCommandTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.EvidencePackCommandTest]/[method:caseInsensitiveFrameworkTokenIsAccepted(java.nio.file.Path)]
removed conditional - replaced equality check with true → KILLED

2.2
Location : writePackMeta
Killed by : org.egothor.methodatlas.EvidencePackCommandTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.EvidencePackCommandTest]/[method:signsManifestWithGeneratedEd25519Keyring(java.nio.file.Path)]
removed conditional - replaced equality check with false → KILLED

352

1.1
Location : writePackMeta
Killed by : org.egothor.methodatlas.EvidencePackCommandTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.EvidencePackCommandTest]/[method:signsManifestWithGeneratedEd25519Keyring(java.nio.file.Path)]
removed conditional - replaced equality check with false → KILLED

2.2
Location : writePackMeta
Killed by : org.egothor.methodatlas.EvidencePackCommandTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.EvidencePackCommandTest]/[method:packMetaJsonRecordsFrameworkAndUnsignedFlag(java.nio.file.Path)]
removed conditional - replaced equality check with true → KILLED

353

1.1
Location : writePackMeta
Killed by : org.egothor.methodatlas.EvidencePackCommandTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.EvidencePackCommandTest]/[method:caseInsensitiveFrameworkTokenIsAccepted(java.nio.file.Path)]
removed conditional - replaced equality check with true → KILLED

2.2
Location : writePackMeta
Killed by : org.egothor.methodatlas.EvidencePackCommandTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.EvidencePackCommandTest]/[method:signsManifestWithGeneratedEd25519Keyring(java.nio.file.Path)]
removed conditional - replaced equality check with false → KILLED

364

1.1
Location : versionString
Killed by : none
removed conditional - replaced equality check with true → SURVIVED
Covering tests

2.2
Location : versionString
Killed by : none
replaced return value with "" for org/egothor/methodatlas/evidence/EvidencePackCommand::versionString → SURVIVED Covering tests

3.3
Location : versionString
Killed by : none
removed conditional - replaced equality check with false → SURVIVED Covering tests

Active mutators

Tests examined


Report generated by PIT 1.22.1