File: WorkflowScripts\AnalyzeCiFailureWorkflowTests.cs
Web Access
Project: src\tests\Infrastructure.Tests\Infrastructure.Tests.csproj (Infrastructure.Tests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Buffers.Binary;
using System.Diagnostics;
using System.IO.Compression;
using System.Text.Json;
using Aspire.TestUtilities;
using Xunit;
 
namespace Infrastructure.Tests;
 
public sealed class AnalyzeCiFailureWorkflowTests(ITestOutputHelper output) : IDisposable
{
    private const string ValidationScriptRelativePath = ".github/workflows/analyze-ci-failure-validation.sh";
    private const string HistoryScriptRelativePath = ".github/workflows/analyze-ci-failure-history.sh";
    private const string CandidatesScriptRelativePath = ".github/workflows/analyze-ci-failure-candidates.sh";
    private const string IssueScriptRelativePath = ".github/workflows/analyze-ci-failure-issue.sh";
    private const string PersistenceScriptRelativePath = ".github/workflows/analyze-ci-failure-persistence.sh";
    private const string CommentScriptRelativePath = ".github/workflows/analyze-ci-failure-comment.sh";
 
    private readonly TemporaryWorkspace _workspace = TemporaryWorkspace.Create(output);
 
    public void Dispose() => _workspace.Dispose();
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task AnalysisValidatorRejectsMismatchedTrustedScope()
    {
        await WriteValidationFixtureAsync(
            """{"run_id":123,"run_scope":"pull-request","verdict":"code-issue","pr":42,"failed_jobs":[],"failed_tests":[],"causes":[]}""",
            """{"run_id":123,"run_scope":"main"}""",
            "[]");
 
        var result = await RunValidationScriptAsync(Path.Combine(_workspace.Path, "output.json"));
 
        Assert.NotEqual(0, result.ExitCode);
        Assert.Contains(
            "::error::Analysis result does not match trusted run context",
            result.Output,
            StringComparison.Ordinal);
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task AnalysisValidatorSkipsPrCommentBudgetWithoutTrustedSubjectPr()
    {
        var failedJobs = Enumerable.Range(1, 150)
            .Select(index => new
            {
                id = index,
                classification = "code-issue",
                reason = new string('r', 500),
            })
            .ToArray();
        await WriteValidationFixtureAsync(
            JsonSerializer.Serialize(new
            {
                run_id = 123,
                run_scope = "pull-request",
                verdict = "code-issue",
                pr = (object?)null,
                failed_jobs = failedJobs,
                failed_tests = Array.Empty<object>(),
                causes = Array.Empty<string>(),
            }),
            """{"run_id":123,"run_scope":"pull-request","pr_numbers":""}""",
            JsonSerializer.Serialize(
                failedJobs.Select(job => new { job.id, name = $"Job {job.id}" })));
 
        var result = await RunValidationScriptAsync(Path.Combine(_workspace.Path, "output.json"));
 
        Assert.Equal(0, result.ExitCode);
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task AnalysisValidatorKeepsRejectedVerdictOnOneWorkflowCommandLine()
    {
        await WriteValidationFixtureAsync(
            """{"run_id":123,"run_scope":"pull-request","verdict":"invalid\n::add-mask::injected","pr":{"number":42},"failed_jobs":[{"id":123,"classification":"code-issue"}],"failed_tests":[],"causes":[]}""",
            """{"run_id":123,"run_scope":"pull-request","pr_numbers":"42"}""",
            """[{"id":123,"name":"Tests"}]""");
 
        var result = await RunValidationScriptAsync(Path.Combine(_workspace.Path, "output.json"));
 
        Assert.NotEqual(0, result.ExitCode);
        Assert.Single(GetWorkflowCommandLines(result.Output));
    }
 
    [Theory]
    [PlatformSpecific(TestPlatforms.AnyUnix)]
    [InlineData("not-json")]
    [InlineData("""{"id":"nuget-timeout","type":"infra-failure","job_ids":[123]}""")]
    [InlineData("""{"id":"nuget-timeout","type":"infra-failure","title":"Failure","error_pattern":"boom","job_ids":[123]}""")]
    [RequiresTools(["bash", "jq"])]
    public async Task AnalysisValidatorKeepsRejectedCauseFilenameOnOneWorkflowCommandLine(string cause)
    {
        await WriteValidationFixtureAsync(
            """{"run_id":123,"run_scope":"pull-request","verdict":"transient-infra","pr":{"number":42},"failed_jobs":[{"id":123,"classification":"transient-infra"}],"failed_tests":[],"causes":["nuget-timeout"]}""",
            """{"run_id":123,"run_scope":"pull-request","pr_numbers":"42"}""",
            """[{"id":123,"name":"Tests"}]""",
            "invalid\n::warning::injected.json",
            cause);
 
        var result = await RunValidationScriptAsync(Path.Combine(_workspace.Path, "output.json"));
 
        Assert.NotEqual(0, result.ExitCode);
        Assert.Single(GetWorkflowCommandLines(result.Output));
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task AnalysisValidatorKeepsRejectedCauseTypeOnOneWorkflowCommandLine()
    {
        await WriteValidationFixtureAsync(
            """{"run_id":123,"run_scope":"pull-request","verdict":"transient-infra","pr":{"number":42},"failed_jobs":[{"id":123,"classification":"transient-infra"}],"failed_tests":[],"causes":["nuget-timeout"]}""",
            """{"run_id":123,"run_scope":"pull-request","pr_numbers":"42"}""",
            """[{"id":123,"name":"Tests"}]""",
            "nuget-timeout.json",
            """{"id":"nuget-timeout","type":"invalid\n::warning::injected","title":"Failure","error_pattern":"boom","job_ids":[123]}""");
 
        var result = await RunValidationScriptAsync(Path.Combine(_workspace.Path, "output.json"));
 
        Assert.NotEqual(0, result.ExitCode);
        Assert.Single(GetWorkflowCommandLines(result.Output));
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task AnalysisValidatorKeepsRejectedPriorCauseTypeOnOneWorkflowCommandLine()
    {
        await WriteValidationFixtureAsync(
            """{"run_id":123,"run_scope":"pull-request","verdict":"transient-infra","pr":{"number":42},"failed_jobs":[{"id":123,"classification":"transient-infra"}],"failed_tests":[],"causes":["nuget-timeout"]}""",
            """{"run_id":123,"run_scope":"pull-request","pr_numbers":"42"}""",
            """[{"id":123,"name":"Tests"}]""",
            "nuget-timeout.json",
            """{"id":"nuget-timeout","type":"infra-failure","title":"Failure","error_pattern":"boom","job_ids":[123]}""");
        var priorCausesDirectory = Directory.CreateDirectory(
            Path.Combine(_workspace.Path, "ci-failure-data", "prior-causes")).FullName;
        await File.WriteAllTextAsync(
            Path.Combine(priorCausesDirectory, "nuget-timeout.json"),
            """{"id":"nuget-timeout","type":"invalid\n::warning::injected"}""");
 
        var result = await RunValidationScriptAsync(Path.Combine(_workspace.Path, "output.json"));
 
        Assert.NotEqual(0, result.ExitCode);
        Assert.Single(GetWorkflowCommandLines(result.Output));
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task AnalysisValidatorRejectsReusedFlakyCauseForDifferentTest()
    {
        await WriteValidationFixtureAsync(
            """{"run_id":123,"run_scope":"pull-request","verdict":"flaky-test","pr":{"number":42},"failed_jobs":[{"id":123,"classification":"flaky-test"}],"failed_tests":[{"name":"Tests.New","job":"Tests","error":"boom","stack_trace":"frame","classification":"flaky","reason":"Intermittent"}],"causes":["flaky-failure"]}""",
            """{"run_id":123,"run_scope":"pull-request","pr_numbers":"42"}""",
            """[{"id":123,"name":"Tests"}]""",
            "flaky-failure.json",
            """{"id":"flaky-failure","type":"flaky-test","title":"Flaky failure","test_name":"Tests.New","error_pattern":"boom","job_ids":[123]}""");
        var priorCausesDirectory = Directory.CreateDirectory(
            Path.Combine(_workspace.Path, "ci-failure-data", "prior-causes")).FullName;
        await File.WriteAllTextAsync(
            Path.Combine(priorCausesDirectory, "flaky-failure.json"),
            """{"id":"flaky-failure","type":"flaky-test","title":"Stored failure","test_name":"Tests.Original","error_pattern":"old"}""");
 
        var result = await RunValidationScriptAsync(Path.Combine(_workspace.Path, "output.json"));
 
        Assert.NotEqual(0, result.ExitCode);
        Assert.Contains(
            "::error::Cause flaky-failure.json cannot change stored test_name",
            result.Output,
            StringComparison.Ordinal);
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task AnalysisValidatorRejectsFailedTestWithoutTrustedEvidence()
    {
        await WriteValidationFixtureAsync(
            """{"run_id":123,"run_scope":"pull-request","verdict":"flaky-test","pr":{"number":42},"failed_jobs":[{"id":123,"classification":"flaky-test"}],"failed_tests":[{"name":"Tests.Invented","job":"Tests","error":"invented","stack_trace":"invented frame","classification":"flaky","reason":"Invented"}],"causes":["flaky-failure"]}""",
            """{"run_id":123,"run_scope":"pull-request","pr_numbers":"42"}""",
            """[{"id":123,"name":"Tests"}]""",
            "flaky-failure.json",
            """{"id":"flaky-failure","type":"flaky-test","title":"Flaky failure","test_name":"Tests.Invented","error_pattern":"invented","job_ids":[123]}""",
            writeTrustedTestFailures: false);
 
        var result = await RunValidationScriptAsync(Path.Combine(_workspace.Path, "output.json"));
 
        Assert.NotEqual(0, result.ExitCode);
        Assert.Contains(
            "::error::Analysis failed_tests do not match trusted test failure evidence",
            result.Output,
            StringComparison.Ordinal);
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task AnalysisValidatorRejectsFlakyCausesWithSwappedJobs()
    {
        await WriteValidationFixtureAsync(
            """
            {"run_id":123,"run_scope":"pull-request","verdict":"flaky-test","pr":{"number":42},
             "failed_jobs":[{"id":1,"classification":"flaky-test"},{"id":2,"classification":"flaky-test"}],
             "failed_tests":[
               {"name":"Tests.First","job":"First job","error":"first","stack_trace":"","classification":"flaky","reason":"Intermittent"},
               {"name":"Tests.Second","job":"Second job","error":"second","stack_trace":"","classification":"flaky","reason":"Intermittent"}],
             "causes":["first-failure","second-failure"]}
            """,
            """{"run_id":123,"run_scope":"pull-request","pr_numbers":"42"}""",
            """[{"id":1,"name":"First job"},{"id":2,"name":"Second job"}]""",
            new Dictionary<string, string>
            {
                ["first-failure.json"] =
                    """{"id":"first-failure","type":"flaky-test","title":"First failure","test_name":"Tests.First","error_pattern":"first","job_ids":[2]}""",
                ["second-failure.json"] =
                    """{"id":"second-failure","type":"flaky-test","title":"Second failure","test_name":"Tests.Second","error_pattern":"second","job_ids":[1]}""",
            });
 
        var result = await RunValidationScriptAsync(Path.Combine(_workspace.Path, "output.json"));
 
        Assert.NotEqual(0, result.ExitCode);
        Assert.Contains(
            "::error::Cause first-failure.json references an unknown or incompatible failed job",
            result.Output,
            StringComparison.Ordinal);
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task AnalysisValidatorRejectsFlakyTestWithoutMatchingCause()
    {
        await WriteValidationFixtureAsync(
            """
            {"run_id":123,"run_scope":"pull-request","verdict":"flaky-test","pr":{"number":42},
             "failed_jobs":[{"id":123,"classification":"flaky-test"}],
             "failed_tests":[
               {"name":"Tests.First","job":"Tests","error":"first","stack_trace":"","classification":"flaky","reason":"Intermittent"},
               {"name":"Tests.Second","job":"Tests","error":"second","stack_trace":"","classification":"flaky","reason":"Intermittent"}],
             "causes":["first-failure"]}
            """,
            """{"run_id":123,"run_scope":"pull-request","pr_numbers":"42"}""",
            """[{"id":123,"name":"Tests"}]""",
            "first-failure.json",
            """{"id":"first-failure","type":"flaky-test","title":"First failure","test_name":"Tests.First","error_pattern":"first","job_ids":[123]}""");
 
        var result = await RunValidationScriptAsync(Path.Combine(_workspace.Path, "output.json"));
 
        Assert.NotEqual(0, result.ExitCode);
        Assert.Contains(
            "::error::Every flaky test and job must be covered by a matching cause",
            result.Output,
            StringComparison.Ordinal);
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task AnalysisValidatorAcceptsCompleteFlakyCoverageWithDuplicateJobNames()
    {
        await WriteValidationFixtureAsync(
            """
            {"run_id":123,"run_scope":"pull-request","verdict":"flaky-test","pr":{"number":42},
             "failed_jobs":[{"id":1,"classification":"flaky-test"},{"id":2,"classification":"flaky-test"}],
             "failed_tests":[
               {"name":"Tests.First","job":"Tests","error":"first","stack_trace":"","classification":"flaky","reason":"Intermittent"},
               {"name":"Tests.Second","job":"Tests","error":"second","stack_trace":"","classification":"flaky","reason":"Intermittent"}],
             "causes":["first-failure","second-failure"]}
            """,
            """{"run_id":123,"run_scope":"pull-request","pr_numbers":"42"}""",
            """[{"id":1,"name":"Tests"},{"id":2,"name":"Tests"}]""",
            new Dictionary<string, string>
            {
                ["first-failure.json"] =
                    """{"id":"first-failure","type":"flaky-test","title":"First failure","test_name":"Tests.First","error_pattern":"first","job_ids":[1]}""",
                ["second-failure.json"] =
                    """{"id":"second-failure","type":"flaky-test","title":"Second failure","test_name":"Tests.Second","error_pattern":"second","job_ids":[2]}""",
            });
 
        var result = await RunValidationScriptAsync(Path.Combine(_workspace.Path, "output.json"));
 
        Assert.Equal(0, result.ExitCode);
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task AnalysisValidatorRedactsCredentialsWhenRebuildingDiagnosticsFromTrustedArtifacts()
    {
        await WriteValidationFixtureAsync(
            """{"run_id":123,"run_scope":"pull-request","verdict":"flaky-test","pr":{"number":42},"failed_jobs":[{"id":123,"classification":"flaky-test"}],"failed_tests":[{"name":"Tests.Flaky","job":"Tests","error":"agent paraphrase","stack_trace":"agent frame","standard_output":"agent stdout","standard_error":"agent stderr","classification":"flaky","reason":"Intermittent"}],"causes":["flaky-failure"]}""",
            """{"run_id":123,"run_scope":"pull-request","pr_numbers":"42"}""",
            """[{"id":123,"name":"Tests"}]""",
            "flaky-failure.json",
            """{"id":"flaky-failure","type":"flaky-test","title":"Flaky failure","test_name":"Tests.Flaky","error_pattern":"Trusted error","job_ids":[123]}""");
        await File.WriteAllTextAsync(
            Path.Combine(_workspace.Path, "ci-failure-data", "test-failures.json"),
            """[{"test":"Tests.Flaky","job":"Tests","error":"Trusted\r\nerror\u001b[31m","stack_trace":"trusted\r\nframe","standard_output":"Authorization: Bearer artifact-secret\r\nassertion context","standard_error":"Server=db;Password='artifact-secret';Timeout=30"}]""");
 
        var result = await RunValidationScriptAsync(Path.Combine(_workspace.Path, "output.json"));
 
        Assert.Equal(0, result.ExitCode);
        using var analysis = JsonDocument.Parse(
            await File.ReadAllTextAsync(Path.Combine(_workspace.Path, "ci-analysis-output", "analysis-result.json")));
        var failedTest = analysis.RootElement.GetProperty("failed_tests")[0];
        Assert.Equal("Trusted\nerror", failedTest.GetProperty("error").GetString());
        Assert.Equal("trusted\nframe", failedTest.GetProperty("stack_trace").GetString());
        Assert.Equal(
            "Authorization: Bearer [REDACTED]\nassertion context",
            failedTest.GetProperty("standard_output").GetString());
        Assert.Equal(
            "Server=db;Password='[REDACTED]';Timeout=30",
            failedTest.GetProperty("standard_error").GetString());
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task AnalysisValidatorRejectsFailedTestAttributedToAnotherJob()
    {
        await WriteValidationFixtureAsync(
            """
            {"run_id":123,"run_scope":"pull-request","verdict":"code-issue","pr":{"number":42},
             "failed_jobs":[{"id":1,"classification":"code-issue"},{"id":2,"classification":"code-issue"}],
             "failed_tests":[{"name":"Tests.Failed","job":"Second job","error":"agent copy","stack_trace":"agent frame","classification":"code-issue","reason":"Deterministic"}],
             "causes":[]}
            """,
            """{"run_id":123,"run_scope":"pull-request","pr_numbers":"42"}""",
            """[{"id":1,"name":"First job"},{"id":2,"name":"Second job"}]""");
        await File.WriteAllTextAsync(
            Path.Combine(_workspace.Path, "ci-failure-data", "test-failures.json"),
            """[{"test":"Tests.Failed","job":"First job","error":"trusted","stack_trace":"trusted frame"}]""");
 
        var result = await RunValidationScriptAsync(Path.Combine(_workspace.Path, "output.json"));
 
        Assert.NotEqual(0, result.ExitCode);
        Assert.Contains(
            "::error::Analysis failed_tests do not match trusted test failure evidence",
            result.Output,
            StringComparison.Ordinal);
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task AnalysisValidatorBindsSameTestNameIndependentlyByJob()
    {
        await WriteValidationFixtureAsync(
            """
            {"run_id":123,"run_scope":"pull-request","verdict":"flaky-test","pr":{"number":42},
             "failed_jobs":[{"id":1,"classification":"flaky-test"},{"id":2,"classification":"flaky-test"}],
             "failed_tests":[
               {"name":"Tests.Flaky","job":"Linux tests","error":"agent copy","stack_trace":"agent frame","classification":"flaky","reason":"Intermittent"},
               {"name":"Tests.Flaky","job":"Windows tests","error":"agent copy","stack_trace":"agent frame","classification":"flaky","reason":"Intermittent"}],
             "causes":["flaky-failure"]}
            """,
            """{"run_id":123,"run_scope":"pull-request","pr_numbers":"42"}""",
            """[{"id":1,"name":"Linux tests"},{"id":2,"name":"Windows tests"}]""",
            "flaky-failure.json",
            """{"id":"flaky-failure","type":"flaky-test","title":"Flaky failure","test_name":"Tests.Flaky","error_pattern":"failure","job_ids":[1,2]}""");
        await File.WriteAllTextAsync(
            Path.Combine(_workspace.Path, "ci-failure-data", "test-failures.json"),
            """
            [
              {"test":"Tests.Flaky","job":"Linux tests","error":"linux failure","stack_trace":"linux frame"},
              {"test":"Tests.Flaky","job":"Windows tests","error":"windows failure","stack_trace":"windows frame"}
            ]
            """);
 
        var result = await RunValidationScriptAsync(Path.Combine(_workspace.Path, "output.json"));
 
        Assert.Equal(0, result.ExitCode);
        using var analysis = JsonDocument.Parse(
            await File.ReadAllTextAsync(Path.Combine(_workspace.Path, "ci-analysis-output", "analysis-result.json")));
        Assert.Collection(
            analysis.RootElement.GetProperty("failed_tests").EnumerateArray(),
            failedTest =>
            {
                Assert.Equal("Linux tests", failedTest.GetProperty("job").GetString());
                Assert.Equal("linux failure", failedTest.GetProperty("error").GetString());
            },
            failedTest =>
            {
                Assert.Equal("Windows tests", failedTest.GetProperty("job").GetString());
                Assert.Equal("windows failure", failedTest.GetProperty("error").GetString());
            });
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task AnalysisValidatorRejectsFailedTestForUnknownJob()
    {
        await WriteValidationFixtureAsync(
            """{"run_id":123,"run_scope":"pull-request","verdict":"code-issue","pr":{"number":42},"failed_jobs":[{"id":123,"classification":"code-issue"}],"failed_tests":[{"name":"Tests.Failed","job":"Unknown","error":"boom","stack_trace":"","classification":"code-issue","reason":"Deterministic"}],"causes":[]}""",
            """{"run_id":123,"run_scope":"pull-request","pr_numbers":"42"}""",
            """[{"id":123,"name":"Tests"}]""");
 
        var result = await RunValidationScriptAsync(Path.Combine(_workspace.Path, "output.json"));
 
        Assert.NotEqual(0, result.ExitCode);
        Assert.Contains(
            "::error::Analysis failed_tests do not match trusted test failure evidence",
            result.Output,
            StringComparison.Ordinal);
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task AnalysisValidatorRejectsUnavailableTestEvidence()
    {
        await WriteValidationFixtureAsync(
            """{"run_id":123,"run_scope":"pull-request","verdict":"transient-infra","pr":{"number":42},"failed_jobs":[{"id":123,"classification":"transient-infra"}],"failed_tests":[],"causes":["nuget-timeout"]}""",
            """{"run_id":123,"run_scope":"pull-request","pr_numbers":"42"}""",
            """[{"id":123,"name":"Tests"}]""",
            "nuget-timeout.json",
            """{"id":"nuget-timeout","type":"infra-failure","title":"NuGet timeout","error_pattern":"timed out","job_ids":[123]}""",
            testEvidenceState: "unavailable");
 
        var result = await RunValidationScriptAsync(Path.Combine(_workspace.Path, "output.json"));
 
        Assert.NotEqual(0, result.ExitCode);
        Assert.Contains(
            "::error::Trusted test evidence is unavailable",
            result.Output,
            StringComparison.Ordinal);
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task AnalysisValidatorAcceptsNotApplicableTestEvidence()
    {
        await WriteValidationFixtureAsync(
            """{"run_id":123,"run_scope":"pull-request","verdict":"transient-infra","pr":{"number":42},"failed_jobs":[{"id":123,"classification":"transient-infra"}],"failed_tests":[],"causes":["nuget-timeout"]}""",
            """{"run_id":123,"run_scope":"pull-request","pr_numbers":"42"}""",
            """[{"id":123,"name":"Build"}]""",
            "nuget-timeout.json",
            """{"id":"nuget-timeout","type":"infra-failure","title":"NuGet timeout","error_pattern":"timed out","job_ids":[123]}""",
            testEvidenceState: "not-applicable");
 
        var result = await RunValidationScriptAsync(Path.Combine(_workspace.Path, "output.json"));
 
        Assert.Equal(0, result.ExitCode);
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task AnalysisValidatorRejectsOmittedTrustedTestFailure()
    {
        await WriteValidationFixtureAsync(
            """{"run_id":123,"run_scope":"pull-request","verdict":"transient-infra","pr":{"number":42},"failed_jobs":[{"id":123,"classification":"transient-infra"}],"failed_tests":[],"causes":["nuget-timeout"]}""",
            """{"run_id":123,"run_scope":"pull-request","pr_numbers":"42"}""",
            """[{"id":123,"name":"Tests"}]""",
            "nuget-timeout.json",
            """{"id":"nuget-timeout","type":"infra-failure","title":"NuGet timeout","error_pattern":"timed out","job_ids":[123]}""");
        await File.WriteAllTextAsync(
            Path.Combine(_workspace.Path, "ci-failure-data", "test-failures.json"),
            """[{"test":"Tests.Failed","job":"Tests","error":"boom","stack_trace":"frame"}]""");
 
        var result = await RunValidationScriptAsync(Path.Combine(_workspace.Path, "output.json"));
 
        Assert.NotEqual(0, result.ExitCode);
        Assert.Contains(
            "::error::Analysis failed_tests do not match trusted test failure evidence",
            result.Output,
            StringComparison.Ordinal);
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task AnalysisValidatorRejectsFlakyCauseForDifferentTest()
    {
        await WriteValidationFixtureAsync(
            """{"run_id":123,"run_scope":"pull-request","verdict":"flaky-test","pr":{"number":42},"failed_jobs":[{"id":123,"classification":"flaky-test"}],"failed_tests":[{"name":"Tests.Flaky","job":"Tests","error":"boom","stack_trace":"","classification":"flaky","reason":"Intermittent"}],"causes":["flaky-failure"]}""",
            """{"run_id":123,"run_scope":"pull-request","pr_numbers":"42"}""",
            """[{"id":123,"name":"Tests"}]""",
            "flaky-failure.json",
            """{"id":"flaky-failure","type":"flaky-test","title":"Flaky failure","test_name":"Tests.Other","error_pattern":"boom","job_ids":[123]}""");
 
        var result = await RunValidationScriptAsync(Path.Combine(_workspace.Path, "output.json"));
 
        Assert.NotEqual(0, result.ExitCode);
        Assert.Contains(
            "::error::Flaky-test cause must reference a validated flaky test",
            result.Output,
            StringComparison.Ordinal);
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task AnalysisValidatorRejectsEmptyFailedTestName()
    {
        await WriteValidationFixtureAsync(
            """{"run_id":123,"run_scope":"pull-request","verdict":"flaky-test","pr":{"number":42},"failed_jobs":[{"id":123,"classification":"flaky-test"}],"failed_tests":[{"name":"","job":"Tests","error":"boom","stack_trace":"","classification":"flaky","reason":"Intermittent"}],"causes":["flaky-failure"]}""",
            """{"run_id":123,"run_scope":"pull-request","pr_numbers":"42"}""",
            """[{"id":123,"name":"Tests"}]""",
            "flaky-failure.json",
            """{"id":"flaky-failure","type":"flaky-test","title":"Flaky failure","test_name":"","error_pattern":"boom","job_ids":[123]}""");
 
        var result = await RunValidationScriptAsync(Path.Combine(_workspace.Path, "output.json"));
 
        Assert.NotEqual(0, result.ExitCode);
        Assert.Contains(
            "::error::Analysis failed_tests must match the safe field schema",
            result.Output,
            StringComparison.Ordinal);
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task AnalysisValidatorAcceptsIdenticalTrustedTestEvidence()
    {
        await WriteValidationFixtureAsync(
            """{"run_id":123,"run_scope":"pull-request","verdict":"flaky-test","pr":{"number":42},"failed_jobs":[{"id":123,"classification":"flaky-test"}],"failed_tests":[{"name":"Tests.Flaky","job":"Tests","error":"agent copy","stack_trace":"agent frame","classification":"flaky","reason":"Intermittent"}],"causes":["flaky-failure"]}""",
            """{"run_id":123,"run_scope":"pull-request","pr_numbers":"42"}""",
            """[{"id":123,"name":"Tests"}]""",
            "flaky-failure.json",
            """{"id":"flaky-failure","type":"flaky-test","title":"Flaky failure","test_name":"Tests.Flaky","error_pattern":"boom","job_ids":[123]}""");
        await File.WriteAllTextAsync(
            Path.Combine(_workspace.Path, "ci-failure-data", "test-failures.json"),
            """[{"test":"Tests.Flaky","job":"Tests","error":"trusted","stack_trace":"frame"},{"test":"Tests.Flaky","job":"Tests","error":"trusted","stack_trace":"frame"}]""");
 
        var result = await RunValidationScriptAsync(Path.Combine(_workspace.Path, "output.json"));
 
        Assert.Equal(0, result.ExitCode);
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task AnalysisValidatorRejectsConflictingTrustedTestEvidence()
    {
        await WriteValidationFixtureAsync(
            """{"run_id":123,"run_scope":"pull-request","verdict":"flaky-test","pr":{"number":42},"failed_jobs":[{"id":123,"classification":"flaky-test"}],"failed_tests":[{"name":"Tests.Flaky","job":"Tests","error":"agent copy","stack_trace":"agent frame","classification":"flaky","reason":"Intermittent"}],"causes":["flaky-failure"]}""",
            """{"run_id":123,"run_scope":"pull-request","pr_numbers":"42"}""",
            """[{"id":123,"name":"Tests"}]""",
            "flaky-failure.json",
            """{"id":"flaky-failure","type":"flaky-test","title":"Flaky failure","test_name":"Tests.Flaky","error_pattern":"boom","job_ids":[123]}""");
        await File.WriteAllTextAsync(
            Path.Combine(_workspace.Path, "ci-failure-data", "test-failures.json"),
            """[{"test":"Tests.Flaky","job":"Tests","error":"linux failure","stack_trace":"linux frame"},{"test":"Tests.Flaky","job":"Tests","error":"windows failure","stack_trace":"windows frame"}]""");
 
        var result = await RunValidationScriptAsync(Path.Combine(_workspace.Path, "output.json"));
 
        Assert.NotEqual(0, result.ExitCode);
        Assert.Contains(
            "::error::Analysis failed_tests do not match trusted test failure evidence",
            result.Output,
            StringComparison.Ordinal);
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task AnalysisValidatorRejectsDuplicateReportedAndTrustedTestPairs()
    {
        await WriteValidationFixtureAsync(
            """{"run_id":123,"run_scope":"pull-request","verdict":"flaky-test","pr":{"number":42},"failed_jobs":[{"id":123,"classification":"flaky-test"}],"failed_tests":[{"name":"Tests.Flaky","job":"Tests","error":"first","stack_trace":"first frame","classification":"flaky","reason":"Intermittent"},{"name":"Tests.Flaky","job":"Tests","error":"second","stack_trace":"second frame","classification":"flaky","reason":"Intermittent"}],"causes":["flaky-failure"]}""",
            """{"run_id":123,"run_scope":"pull-request","pr_numbers":"42"}""",
            """[{"id":123,"name":"Tests"}]""",
            "flaky-failure.json",
            """{"id":"flaky-failure","type":"flaky-test","title":"Flaky failure","test_name":"Tests.Flaky","error_pattern":"boom","job_ids":[123]}""");
        await File.WriteAllTextAsync(
            Path.Combine(_workspace.Path, "ci-failure-data", "test-failures.json"),
            """[{"test":"Tests.Flaky","job":"Tests","error":"first","stack_trace":"first frame"},{"test":"Tests.Flaky","job":"Tests","error":"second","stack_trace":"second frame"}]""");
 
        var result = await RunValidationScriptAsync(Path.Combine(_workspace.Path, "output.json"));
 
        Assert.NotEqual(0, result.ExitCode);
        Assert.Contains(
            "::error::Analysis failed_tests do not match trusted test failure evidence",
            result.Output,
            StringComparison.Ordinal);
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task AnalysisValidatorRejectsMoreThanTenCausesBeforeProcessingCauseFiles()
    {
        var causeIds = Enumerable.Range(1, 11).Select(index => $"cause-{index}").ToArray();
        var causes = causeIds.ToDictionary(
            causeId => $"{causeId}.json",
            causeId => CreateCause(causeId, "infra-failure", 123));
        await WriteValidationFixtureAsync(
            JsonSerializer.Serialize(new
            {
                run_id = 123,
                run_scope = "pull-request",
                verdict = "transient-infra",
                pr = new { number = 42 },
                failed_jobs = new[] { new { id = 123, classification = "transient-infra" } },
                failed_tests = Array.Empty<object>(),
                causes = causeIds,
            }),
            """{"run_id":123,"run_scope":"pull-request","pr_numbers":"42"}""",
            """[{"id":123,"name":"Tests"}]""",
            causes);
 
        var result = await RunValidationScriptAsync(Path.Combine(_workspace.Path, "output.json"));
 
        Assert.NotEqual(0, result.ExitCode);
        Assert.Contains(
            "::error::Analysis exceeds the 10-cause publication budget",
            result.Output,
            StringComparison.Ordinal);
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task AnalysisValidatorRejectsMoreThanTenRawCauseFilesBeforeParsingThem()
    {
        var causeIds = Enumerable.Range(1, 10).Select(index => $"cause-{index}").ToArray();
        var causes = causeIds.ToDictionary(
            causeId => $"{causeId}.json",
            causeId => CreateCause(causeId, "infra-failure", 123));
        causes["unreferenced.json"] = "not-json";
        await WriteValidationFixtureAsync(
            JsonSerializer.Serialize(new
            {
                run_id = 123,
                run_scope = "pull-request",
                verdict = "transient-infra",
                pr = new { number = 42 },
                failed_jobs = new[] { new { id = 123, classification = "transient-infra" } },
                failed_tests = Array.Empty<object>(),
                causes = causeIds,
            }),
            """{"run_id":123,"run_scope":"pull-request","pr_numbers":"42"}""",
            """[{"id":123,"name":"Tests"}]""",
            causes);
 
        var result = await RunValidationScriptAsync(Path.Combine(_workspace.Path, "output.json"));
 
        Assert.NotEqual(0, result.ExitCode);
        Assert.Contains(
            "::error::Analysis exceeds the 10-cause publication budget",
            result.Output,
            StringComparison.Ordinal);
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task AnalysisValidatorRejectsOversizedRenderedPrComment()
    {
        var failedJobs = Enumerable.Range(1, 150)
            .Select(index => new
            {
                id = index,
                classification = "code-issue",
                reason = new string('r', 500),
            })
            .ToArray();
        await WriteValidationFixtureAsync(
            JsonSerializer.Serialize(new
            {
                run_id = 123,
                run_scope = "pull-request",
                verdict = "code-issue",
                pr = new { number = 42 },
                failed_jobs = failedJobs,
                failed_tests = Array.Empty<object>(),
                causes = Array.Empty<string>(),
            }),
            """{"run_id":123,"run_scope":"pull-request","pr_numbers":"42"}""",
            JsonSerializer.Serialize(
                failedJobs.Select(job => new { job.id, name = $"Job {job.id}" })));
 
        var result = await RunValidationScriptAsync(Path.Combine(_workspace.Path, "output.json"));
 
        Assert.NotEqual(0, result.ExitCode);
        Assert.Contains(
            "::error::Rendered PR comment exceeds the 65000-byte publication budget",
            result.Output,
            StringComparison.Ordinal);
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task AnalysisValidatorRequiresTrustedRunMetadataForPrComment()
    {
        await WriteValidationFixtureAsync(
            """{"run_id":123,"run_scope":"pull-request","verdict":"code-issue","pr":{"number":42},"failed_jobs":[{"id":123,"classification":"code-issue"}],"failed_tests":[],"causes":[]}""",
            """{"run_id":123,"run_scope":"pull-request","pr_numbers":"42"}""",
            """[{"id":123,"name":"Tests"}]""");
        File.Delete(Path.Combine(_workspace.Path, "ci-failure-data", "run.json"));
 
        var result = await RunValidationScriptAsync(Path.Combine(_workspace.Path, "output.json"));
 
        Assert.NotEqual(0, result.ExitCode);
        Assert.Contains(
            "::error::Analysis result or trusted run data not found",
            result.Output,
            StringComparison.Ordinal);
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task AnalysisValidatorRejectsUnsafeTrustedRunUrl()
    {
        await WriteValidationFixtureAsync(
            """{"run_id":123,"run_scope":"pull-request","verdict":"code-issue","pr":{"number":42},"failed_jobs":[{"id":123,"classification":"code-issue"}],"failed_tests":[],"causes":[]}""",
            """{"run_id":123,"run_scope":"pull-request","pr_numbers":"42"}""",
            """[{"id":123,"name":"Tests"}]""");
        await File.WriteAllTextAsync(
            Path.Combine(_workspace.Path, "ci-failure-data", "run.json"),
            """{"id":123,"html_url":"https://github.com/microsoft\n@reviewers/aspire/actions/runs/123"}""");
 
        var result = await RunValidationScriptAsync(Path.Combine(_workspace.Path, "output.json"));
 
        Assert.NotEqual(0, result.ExitCode);
        Assert.Contains(
            "::error::Trusted run metadata is invalid",
            result.Output,
            StringComparison.Ordinal);
    }
 
    [Theory]
    [InlineData(
        """{"run_id":123,"run_scope":"main","verdict":"main-repository-breakage","pr":42,"failed_jobs":[],"failed_tests":[],"causes":[]}""",
        """{"run_id":123,"run_scope":"main"}""",
        "[]",
        "::error::Main run analysis must not identify a subject PR")]
    [InlineData(
        """{"run_id":123,"run_scope":"pull-request","verdict":"code-issue","pr":{"number":42},"failed_jobs":[{"id":456,"classification":"code-issue"}],"failed_tests":[],"causes":[]}""",
        """{"run_id":123,"run_scope":"pull-request","pr_numbers":"42"}""",
        """[{"id":123,"name":"Tests"}]""",
        "::error::Analysis failed-job IDs do not match the trusted failed jobs")]
    [InlineData(
        """{"run_id":123,"run_scope":"pull-request","verdict":"transient-infra","pr":{"number":42},"failed_jobs":[{"id":123,"classification":"transient-infra"}],"failed_tests":[],"causes":[]}""",
        """{"run_id":123,"run_scope":"pull-request","pr_numbers":"42"}""",
        """[{"id":123,"name":"Tests"}]""",
        "::error::A transient-infra verdict requires every failed job and cause to be an infrastructure failure")]
    [InlineData(
        """{"run_id":123,"run_scope":"pull-request","verdict":"code-issue","pr":{"number":999},"failed_jobs":[{"id":123,"classification":"code-issue"}],"failed_tests":[],"causes":[]}""",
        """{"run_id":123,"run_scope":"pull-request","pr_numbers":"42"}""",
        """[{"id":123,"name":"Tests"}]""",
        "::error::Pull request analysis must identify a trusted subject PR")]
    [InlineData(
        """{"run_id":123,"run_scope":"pull-request","verdict":"code-issue","pr":{},"failed_jobs":[{"id":123,"classification":"code-issue"}],"failed_tests":[],"causes":[]}""",
        """{"run_id":123,"run_scope":"pull-request","pr_numbers":""}""",
        """[{"id":123,"name":"Tests"}]""",
        "::error::Pull request analysis must identify a trusted subject PR")]
    [InlineData(
        """{"run_id":123,"run_scope":"pull-request","verdict":"code-issue","pr":{"number":42},"failed_jobs":[{"id":123,"classification":"code-issue"}],"failed_tests":[],"causes":[]}""",
        """{"run_id":123,"run_scope":"pull-request","pr_numbers":"42,43"}""",
        """[{"id":123,"name":"Tests"}]""",
        "::error::Trusted run context must contain one unambiguous subject PR")]
    [RequiresTools(["bash", "jq"])]
    public async Task AnalysisValidatorRejectsUntrustedAssociations(
        string analysis,
        string runContext,
        string trustedFailedJobs,
        string expectedError)
    {
        await WriteValidationFixtureAsync(analysis, runContext, trustedFailedJobs);
 
        var result = await RunValidationScriptAsync(Path.Combine(_workspace.Path, "output.json"));
 
        Assert.NotEqual(0, result.ExitCode);
        Assert.Contains(expectedError, result.Output, StringComparison.Ordinal);
    }
 
    [Theory]
    [InlineData(
        """{"run_id":123,"run_scope":"pull-request","verdict":"transient-infra","pr":{"number":42},"failed_jobs":[{"id":123,"classification":"transient-infra"}],"causes":["nuget-timeout"]}""")]
    [InlineData(
        """{"run_id":123,"run_scope":"pull-request","verdict":"transient-infra","pr":{"number":42},"failed_jobs":[{"id":123,"classification":"transient-infra"}],"failed_tests":{},"causes":["nuget-timeout"]}""")]
    [InlineData(
        """{"run_id":123,"run_scope":"pull-request","verdict":"transient-infra","pr":{"number":42},"failed_jobs":[{"id":123,"classification":"transient-infra"}],"failed_tests":["not-an-object"],"causes":["nuget-timeout"]}""")]
    [InlineData(
        """{"run_id":123,"run_scope":"pull-request","verdict":"transient-infra","pr":{"number":42},"failed_jobs":[{"id":123,"classification":"transient-infra"}],"failed_tests":[{"name":"Tests.Flaky","job":"Tests","error":"boom","stack_trace":123,"classification":"flaky","reason":"Intermittent"}],"causes":["nuget-timeout"]}""")]
    [InlineData(
        """{"run_id":123,"run_scope":"pull-request","verdict":"transient-infra","pr":{"number":42},"failed_jobs":[{"id":123,"classification":"transient-infra"}],"failed_tests":[{"name":"Tests.Flaky","job":"Tests","error":"boom","stack_trace":"","classification":"maybe","reason":"Intermittent"}],"causes":["nuget-timeout"]}""")]
    [InlineData(
        """{"run_id":123,"run_scope":"pull-request","verdict":"transient-infra","pr":{"number":42},"failed_jobs":[{"id":123,"classification":"transient-infra"}],"failed_tests":[{"name":123,"job":"Tests","error":"boom","stack_trace":"","classification":"flaky","reason":"Intermittent"}],"causes":["nuget-timeout"]}""")]
    [InlineData(
        """{"run_id":123,"run_scope":"pull-request","verdict":"transient-infra","pr":{"number":42},"failed_jobs":[{"id":123,"classification":"transient-infra"}],"failed_tests":[{"name":"Tests.Flaky","job":{},"error":"boom","stack_trace":"","classification":"flaky","reason":"Intermittent"}],"causes":["nuget-timeout"]}""")]
    [InlineData(
        """{"run_id":123,"run_scope":"pull-request","verdict":"transient-infra","pr":{"number":42},"failed_jobs":[{"id":123,"classification":"transient-infra"}],"failed_tests":[{"name":"Tests.Flaky","job":"Tests","error":[],"stack_trace":"","classification":"flaky","reason":"Intermittent"}],"causes":["nuget-timeout"]}""")]
    [InlineData(
        """{"run_id":123,"run_scope":"pull-request","verdict":"transient-infra","pr":{"number":42},"failed_jobs":[{"id":123,"classification":"transient-infra"}],"failed_tests":[{"name":"Tests.Flaky","job":"Tests","error":"boom","stack_trace":"","classification":"flaky","reason":false}],"causes":["nuget-timeout"]}""")]
    [RequiresTools(["bash", "jq"])]
    public async Task AnalysisValidatorRejectsMalformedFailedTests(string analysis)
    {
        await WriteValidationFixtureAsync(
            analysis,
            """{"run_id":123,"run_scope":"pull-request","pr_numbers":"42"}""",
            """[{"id":123,"name":"Tests"}]""",
            "nuget-timeout.json",
            """{"id":"nuget-timeout","type":"infra-failure","title":"NuGet timeout","error_pattern":"Request timed out","job_ids":[123]}""");
 
        var result = await RunValidationScriptAsync(Path.Combine(_workspace.Path, "output.json"));
 
        Assert.NotEqual(0, result.ExitCode);
        Assert.Contains(
            "::error::Analysis failed_tests must match the safe field schema",
            result.Output,
            StringComparison.Ordinal);
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task AnalysisValidatorSanitizesAndBoundsPublishedDiagnosticText()
    {
        var analysis = JsonSerializer.Serialize(new
        {
            run_id = 123,
            run_scope = "pull-request",
            verdict = "code-issue",
            pr = new { number = 42 },
            failed_jobs = new[]
            {
                new
                {
                    id = 123,
                    classification = "code-issue",
                    reason = "Job\r\n[link](https://evil.example)\u202E" + new string('j', 600),
                },
            },
            failed_tests = new[]
            {
                new
                {
                    name = "Tests.Flaky\n![image](https://evil.example/image.png)" + new string('n', 600),
                    job = "Tests",
                    error = "Failure\u001b[31m\r\n# heading\n```" + new string('e', 1100),
                    stack_trace = "frame\r\n@reviewers\u00AD" + new string('s', 2100),
                    classification = "code-issue",
                    reason = "Deterministic\r\n[details](https://evil.example)" + new string('r', 600),
                },
            },
            causes = Array.Empty<string>(),
        });
        await WriteValidationFixtureAsync(
            analysis,
            """{"run_id":123,"run_scope":"pull-request","pr_numbers":"42"}""",
            """[{"id":123,"name":"Tests"}]""");
 
        var result = await RunValidationScriptAsync(Path.Combine(_workspace.Path, "output.json"));
 
        Assert.Equal(0, result.ExitCode);
        using var sanitized = JsonDocument.Parse(
            await File.ReadAllTextAsync(Path.Combine(_workspace.Path, "ci-analysis-output", "analysis-result.json")));
        var failedJob = sanitized.RootElement.GetProperty("failed_jobs")[0];
        Assert.Equal(500, failedJob.GetProperty("reason").GetString()!.Length);
        Assert.StartsWith("Job [link](https://evil.example)", failedJob.GetProperty("reason").GetString(), StringComparison.Ordinal);
        var failedTest = sanitized.RootElement.GetProperty("failed_tests")[0];
        Assert.Equal(500, failedTest.GetProperty("name").GetString()!.Length);
        Assert.StartsWith("Tests.Flaky ![image](https://evil.example/image.png)", failedTest.GetProperty("name").GetString(), StringComparison.Ordinal);
        Assert.Equal(1000, failedTest.GetProperty("error").GetString()!.Length);
        Assert.StartsWith("Failure\n# heading\n```", failedTest.GetProperty("error").GetString(), StringComparison.Ordinal);
        Assert.Equal(2000, failedTest.GetProperty("stack_trace").GetString()!.Length);
        Assert.StartsWith("frame\n@reviewers", failedTest.GetProperty("stack_trace").GetString(), StringComparison.Ordinal);
        Assert.Equal(500, failedTest.GetProperty("reason").GetString()!.Length);
        Assert.StartsWith("Deterministic [details](https://evil.example)", failedTest.GetProperty("reason").GetString(), StringComparison.Ordinal);
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task AnalysisValidatorRejectsFailedTestsInTransientInfraVerdict()
    {
        await WriteValidationFixtureAsync(
            """{"run_id":123,"run_scope":"pull-request","verdict":"transient-infra","pr":{"number":42},"failed_jobs":[{"id":123,"classification":"transient-infra"}],"failed_tests":[{"name":"Tests.Flaky","job":"Tests","error":"boom","stack_trace":"","classification":"flaky","reason":"Intermittent"}],"causes":["nuget-timeout"]}""",
            """{"run_id":123,"run_scope":"pull-request","pr_numbers":"42"}""",
            """[{"id":123,"name":"Tests"}]""",
            "nuget-timeout.json",
            """{"id":"nuget-timeout","type":"infra-failure","title":"NuGet timeout","error_pattern":"Request timed out","job_ids":[123]}""");
 
        var result = await RunValidationScriptAsync(Path.Combine(_workspace.Path, "output.json"));
 
        Assert.NotEqual(0, result.ExitCode);
        Assert.Contains(
            "::error::Analysis failed_tests are incompatible with verdict transient-infra",
            result.Output,
            StringComparison.Ordinal);
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task AnalysisValidatorRejectsCodeIssueTestInFlakyVerdict()
    {
        await WriteValidationFixtureAsync(
            """{"run_id":123,"run_scope":"pull-request","verdict":"flaky-test","pr":{"number":42},"failed_jobs":[{"id":123,"classification":"flaky-test"}],"failed_tests":[{"name":"Tests.Deterministic","job":"Tests","error":"boom","stack_trace":"","classification":"code-issue","reason":"Deterministic"}],"causes":["flaky-failure"]}""",
            """{"run_id":123,"run_scope":"pull-request","pr_numbers":"42"}""",
            """[{"id":123,"name":"Tests"}]""",
            "flaky-failure.json",
            """{"id":"flaky-failure","type":"flaky-test","title":"Flaky test","error_pattern":"Tests.Deterministic","job_ids":[123]}""");
 
        var result = await RunValidationScriptAsync(Path.Combine(_workspace.Path, "output.json"));
 
        Assert.NotEqual(0, result.ExitCode);
        Assert.Contains(
            "::error::Analysis failed_tests are incompatible with verdict flaky-test",
            result.Output,
            StringComparison.Ordinal);
    }
 
    [Theory]
    [InlineData(
        """{"run_id":123,"run_scope":"pull-request","verdict":"transient-infra","pr":{"number":42},"failed_jobs":[{"id":123,"classification":"transient-infra"}],"failed_tests":[],"causes":["nuget-timeout"]}""",
        """{"run_id":123,"run_scope":"pull-request","pr_numbers":"42"}""",
        "nuget-timeout.json",
        """{"id":"nuget-timeout","type":"infra-failure","title":"NuGet timeout","error_pattern":"Request timed out","job_ids":[123]}""")]
    [InlineData(
        """{"run_id":123,"run_scope":"pull-request","verdict":"transient-infra","pr":null,"failed_jobs":[{"id":123,"classification":"transient-infra"}],"failed_tests":[],"causes":["nuget-timeout"]}""",
        """{"run_id":123,"run_scope":"pull-request","pr_numbers":"42"}""",
        "nuget-timeout.json",
        """{"id":"nuget-timeout","type":"infra-failure","title":"NuGet timeout","error_pattern":"Request timed out","job_ids":[123]}""")]
    [InlineData(
        """{"run_id":123,"run_scope":"pull-request","verdict":"transient-infra","pr":null,"failed_jobs":[{"id":123,"classification":"transient-infra"}],"failed_tests":[],"causes":["nuget-timeout"]}""",
        """{"run_id":123,"run_scope":"pull-request","pr_numbers":""}""",
        "nuget-timeout.json",
        """{"id":"nuget-timeout","type":"infra-failure","title":"NuGet timeout","error_pattern":"Request timed out","job_ids":[123]}""")]
    [InlineData(
        """{"run_id":123,"run_scope":"main","verdict":"main-repository-breakage","pr":null,"failed_jobs":[{"id":123,"classification":"main-repository-breakage"}],"failed_tests":[],"causes":["main-build-break"]}""",
        """{"run_id":123,"run_scope":"main","pr_numbers":""}""",
        "main-build-break.json",
        """{"id":"main-build-break","type":"main-repository-breakage","title":"Main build break","error_pattern":"Compilation failed","job_ids":[123]}""")]
    [InlineData(
        """{"run_id":123,"run_scope":"pull-request","verdict":"flaky-test","pr":{"number":42},"failed_jobs":[{"id":123,"classification":"flaky-test"}],"failed_tests":[{"name":"Tests.Flaky","job":"Tests","error":"boom","stack_trace":"","classification":"flaky","reason":"Intermittent"}],"causes":["flaky-failure"]}""",
        """{"run_id":123,"run_scope":"pull-request","pr_numbers":"42"}""",
        "flaky-failure.json",
        """{"id":"flaky-failure","type":"flaky-test","title":"Flaky test","test_name":"Tests.Flaky","error_pattern":"Tests.Flaky","job_ids":[123]}""")]
    [InlineData(
        """{"run_id":123,"run_scope":"pull-request","verdict":"flaky-test","pr":{"number":42},"failed_jobs":[{"id":123,"classification":"flaky-test"}],"failed_tests":[{"name":"Tests.Flaky","job":"Tests","error":"boom","stack_trace":null,"classification":"flaky","reason":"Intermittent"}],"causes":["flaky-failure"]}""",
        """{"run_id":123,"run_scope":"pull-request","pr_numbers":"42"}""",
        "flaky-failure.json",
        """{"id":"flaky-failure","type":"flaky-test","title":"Flaky test","test_name":"Tests.Flaky","error_pattern":"Tests.Flaky","job_ids":[123]}""")]
    [InlineData(
        """{"run_id":123,"run_scope":"pull-request","verdict":"flaky-test","pr":{"number":42},"failed_jobs":[{"id":123,"classification":"flaky-test"}],"failed_tests":[{"name":"Tests.Flaky","job":"Tests","error":"boom","classification":"flaky","reason":"Intermittent"}],"causes":["flaky-failure"]}""",
        """{"run_id":123,"run_scope":"pull-request","pr_numbers":"42"}""",
        "flaky-failure.json",
        """{"id":"flaky-failure","type":"flaky-test","title":"Flaky test","test_name":"Tests.Flaky","error_pattern":"Tests.Flaky","job_ids":[123]}""")]
    [RequiresTools(["bash", "jq"])]
    public async Task AnalysisValidatorAcceptsValidResults(
        string analysis,
        string runContext,
        string causeFileName,
        string cause)
    {
        await WriteValidationFixtureAsync(
            analysis,
            runContext,
            """[{"id":123,"name":"Tests"}]""",
            causeFileName,
            cause);
 
        var result = await RunValidationScriptAsync(Path.Combine(_workspace.Path, "output.json"));
 
        Assert.Equal(0, result.ExitCode);
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task AnalysisValidatorRejectsCauseWithoutJobIds()
    {
        await WriteValidationFixtureAsync(
            """{"run_id":123,"run_scope":"pull-request","verdict":"transient-infra","pr":{"number":42},"failed_jobs":[{"id":123,"classification":"transient-infra"}],"failed_tests":[],"causes":["nuget-timeout"]}""",
            """{"run_id":123,"run_scope":"pull-request","pr_numbers":"42"}""",
            """[{"id":123,"name":"Tests"}]""",
            "nuget-timeout.json",
            """{"id":"nuget-timeout","type":"infra-failure","title":"NuGet timeout","error_pattern":"Request timed out"}""");
 
        var result = await RunValidationScriptAsync(Path.Combine(_workspace.Path, "output.json"));
 
        Assert.NotEqual(0, result.ExitCode);
        Assert.Contains(
            "::error::Cause nuget-timeout.json contains unsupported or publisher-owned fields",
            result.Output,
            StringComparison.Ordinal);
    }
 
    [Theory]
    [InlineData("title", "   ")]
    [InlineData("error_pattern", "  ")]
    [InlineData("test_name", "Tests.Infrastructure")]
    [RequiresTools(["bash", "jq"])]
    public async Task AnalysisValidatorRejectsInvalidInfrastructureCauseFields(string field, string value)
    {
        await WriteValidationFixtureAsync(
            """{"run_id":123,"run_scope":"pull-request","verdict":"transient-infra","pr":{"number":42},"failed_jobs":[{"id":123,"classification":"transient-infra"}],"failed_tests":[],"causes":["nuget-timeout"]}""",
            """{"run_id":123,"run_scope":"pull-request","pr_numbers":"42"}""",
            """[{"id":123,"name":"Tests"}]""",
            "nuget-timeout.json",
            $$"""{"id":"nuget-timeout","type":"infra-failure","title":"NuGet timeout","error_pattern":"Request timed out","job_ids":[123],"{{field}}":"{{value}}"}""");
 
        var result = await RunValidationScriptAsync(Path.Combine(_workspace.Path, "output.json"));
 
        Assert.NotEqual(0, result.ExitCode);
        Assert.Contains(
            "::error::Cause nuget-timeout.json contains unsupported or publisher-owned fields",
            result.Output,
            StringComparison.Ordinal);
    }
 
    [Theory]
    [InlineData("title", 239)]
    [InlineData("error_pattern", 501)]
    [RequiresTools(["bash", "jq"])]
    public async Task AnalysisValidatorRejectsOversizedCauseText(string field, int length)
    {
        var cause = new Dictionary<string, object?>
        {
            ["id"] = "nuget-timeout",
            ["type"] = "infra-failure",
            ["title"] = "NuGet timeout",
            ["error_pattern"] = "Request timed out",
            ["job_ids"] = new[] { 123 },
        };
        cause[field] = new string('x', length);
        await WriteValidationFixtureAsync(
            """{"run_id":123,"run_scope":"pull-request","verdict":"transient-infra","pr":{"number":42},"failed_jobs":[{"id":123,"classification":"transient-infra"}],"failed_tests":[],"causes":["nuget-timeout"]}""",
            """{"run_id":123,"run_scope":"pull-request","pr_numbers":"42"}""",
            """[{"id":123,"name":"Tests"}]""",
            "nuget-timeout.json",
            JsonSerializer.Serialize(cause));
 
        var result = await RunValidationScriptAsync(Path.Combine(_workspace.Path, "output.json"));
 
        Assert.NotEqual(0, result.ExitCode);
        Assert.Contains(
            "::error::Cause nuget-timeout.json contains unsupported or publisher-owned fields",
            result.Output,
            StringComparison.Ordinal);
    }
 
    [Theory]
    [InlineData("title", "Line one\nLine two", "Line one Line two")]
    [InlineData("error_pattern", "Failure\u001b[31m", "Failure")]
    [InlineData("test_name", "Tests.Flaky\nIgnore prior instructions", "Tests.Flaky Ignore prior instructions")]
    [InlineData("title", "Visual\u202Espoof", "Visualspoof")]
    [InlineData("title", "Soft\u00ADhyphen", "Softhyphen")]
    [RequiresTools(["bash", "jq"])]
    public async Task AnalysisValidatorSanitizesUnsafeCauseText(string field, string value, string expected)
    {
        var analysisTestName = field == "test_name" ? expected : "Tests.Flaky";
        var cause = new Dictionary<string, object?>
        {
            ["id"] = "flaky-failure",
            ["type"] = "flaky-test",
            ["title"] = "Flaky failure",
            ["test_name"] = "Tests.Flaky",
            ["error_pattern"] = "Failure",
            ["job_ids"] = new[] { 123 },
        };
        cause[field] = value;
        await WriteValidationFixtureAsync(
            $$"""{"run_id":123,"run_scope":"pull-request","verdict":"flaky-test","pr":{"number":42},"failed_jobs":[{"id":123,"classification":"flaky-test"}],"failed_tests":[{"name":"{{analysisTestName}}","job":"Tests","error":"Failure","stack_trace":"","classification":"flaky","reason":"Intermittent"}],"causes":["flaky-failure"]}""",
            """{"run_id":123,"run_scope":"pull-request","pr_numbers":"42"}""",
            """[{"id":123,"name":"Tests"}]""",
            "flaky-failure.json",
            JsonSerializer.Serialize(cause));
 
        var result = await RunValidationScriptAsync(Path.Combine(_workspace.Path, "output.json"));
 
        Assert.Equal(0, result.ExitCode);
        using var sanitizedCause = JsonDocument.Parse(
            await File.ReadAllTextAsync(Path.Combine(_workspace.Path, "ci-analysis-output", "causes", "flaky-failure.json")));
        Assert.Equal(expected, sanitizedCause.RootElement.GetProperty(field).GetString());
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task AnalysisValidatorRejectsOversizedTestName()
    {
        await WriteValidationFixtureAsync(
            """{"run_id":123,"run_scope":"pull-request","verdict":"flaky-test","pr":{"number":42},"failed_jobs":[{"id":123,"classification":"flaky-test"}],"failed_tests":[{"name":"Tests.Flaky","job":"Tests","error":"Failure","stack_trace":"","classification":"flaky","reason":"Intermittent"}],"causes":["flaky-failure"]}""",
            """{"run_id":123,"run_scope":"pull-request","pr_numbers":"42"}""",
            """[{"id":123,"name":"Tests"}]""",
            "flaky-failure.json",
            JsonSerializer.Serialize(new
            {
                id = "flaky-failure",
                type = "flaky-test",
                title = "Flaky failure",
                test_name = new string('x', 501),
                error_pattern = "Failure",
                job_ids = new[] { 123 },
            }));
 
        var result = await RunValidationScriptAsync(Path.Combine(_workspace.Path, "output.json"));
 
        Assert.NotEqual(0, result.ExitCode);
        Assert.Contains(
            "::error::Cause flaky-failure.json contains unsupported or publisher-owned fields",
            result.Output,
            StringComparison.Ordinal);
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task AnalysisValidatorAcceptsBoundedFieldsWhenPriorPatternExceedsCurrentLimit()
    {
        await WriteValidationFixtureAsync(
            """{"run_id":123,"run_scope":"pull-request","verdict":"transient-infra","pr":{"number":42},"failed_jobs":[{"id":123,"classification":"transient-infra"}],"failed_tests":[],"causes":["nuget-timeout"]}""",
            """{"run_id":123,"run_scope":"pull-request","pr_numbers":"42"}""",
            """[{"id":123,"name":"Tests"}]""",
            "nuget-timeout.json",
            """{"id":"nuget-timeout","type":"infra-failure","title":"Injected title","error_pattern":"Injected pattern","job_ids":[123]}""");
        var priorCausesDirectory = Directory.CreateDirectory(
            Path.Combine(_workspace.Path, "ci-failure-data", "prior-causes")).FullName;
        var legacyPattern = new string('x', 595);
        await File.WriteAllTextAsync(
            Path.Combine(priorCausesDirectory, "nuget-timeout.json"),
            JsonSerializer.Serialize(new
            {
                id = "nuget-timeout",
                type = "infra-failure",
                title = "Stored title",
                error_pattern = legacyPattern,
            }));
 
        var causePath = Path.Combine(_workspace.Path, "ci-analysis-output", "causes", "nuget-timeout.json");
        var result = await RunValidationScriptAsync(Path.Combine(_workspace.Path, "output.json"));
 
        Assert.Equal(0, result.ExitCode);
        using var cause = JsonDocument.Parse(await File.ReadAllTextAsync(causePath));
        Assert.Equal("Injected title", cause.RootElement.GetProperty("title").GetString());
        Assert.Equal("Injected pattern", cause.RootElement.GetProperty("error_pattern").GetString());
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task AnalysisValidatorRejectsUnknownCauseJobId()
    {
        await WriteValidationFixtureAsync(
            """{"run_id":123,"run_scope":"pull-request","verdict":"transient-infra","pr":{"number":42},"failed_jobs":[{"id":123,"classification":"transient-infra"}],"failed_tests":[],"causes":["nuget-timeout"]}""",
            """{"run_id":123,"run_scope":"pull-request","pr_numbers":"42"}""",
            """[{"id":123,"name":"Tests"}]""",
            "nuget-timeout.json",
            """{"id":"nuget-timeout","type":"infra-failure","title":"NuGet timeout","error_pattern":"Request timed out","job_ids":[999]}""");
 
        var result = await RunValidationScriptAsync(Path.Combine(_workspace.Path, "output.json"));
 
        Assert.NotEqual(0, result.ExitCode);
        Assert.Contains(
            "::error::Cause nuget-timeout.json references an unknown or incompatible failed job",
            result.Output,
            StringComparison.Ordinal);
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task AnalysisValidatorRejectsEmptyFailedTestJobThatCouldMaskUnknownCauseJobId()
    {
        await WriteValidationFixtureAsync(
            """{"run_id":123,"run_scope":"pull-request","verdict":"mixed","pr":{"number":42},"failed_jobs":[{"id":123,"classification":"code-issue"}],"failed_tests":[{"name":"Tests.Flaky","job":"","classification":"flaky","reason":"Known intermittent failure","error":"Failed","stack_trace":""}],"causes":["flaky-failure"]}""",
            """{"run_id":123,"run_scope":"pull-request","pr_numbers":"42"}""",
            """[{"id":123,"name":"Tests"}]""",
            "flaky-failure.json",
            """{"id":"flaky-failure","type":"flaky-test","title":"Flaky failure","error_pattern":"Failed","test_name":"Tests.Flaky","job_ids":[999]}""");
 
        var result = await RunValidationScriptAsync(Path.Combine(_workspace.Path, "output.json"));
 
        Assert.NotEqual(0, result.ExitCode);
        Assert.Contains(
            "::error::Analysis failed_tests must match the safe field schema",
            result.Output,
            StringComparison.Ordinal);
    }
 
    [Theory]
    [InlineData("[]")]
    [InlineData("[\"123\"]")]
    [InlineData("[123,123]")]
    [InlineData("[0]")]
    [InlineData("[1.5]")]
    [RequiresTools(["bash", "jq"])]
    public async Task AnalysisValidatorRejectsMalformedCauseJobIds(string jobIds)
    {
        await WriteValidationFixtureAsync(
            """{"run_id":123,"run_scope":"pull-request","verdict":"transient-infra","pr":{"number":42},"failed_jobs":[{"id":123,"classification":"transient-infra"}],"failed_tests":[],"causes":["nuget-timeout"]}""",
            """{"run_id":123,"run_scope":"pull-request","pr_numbers":"42"}""",
            """[{"id":123,"name":"Tests"}]""",
            "nuget-timeout.json",
            $$"""{"id":"nuget-timeout","type":"infra-failure","title":"NuGet timeout","error_pattern":"Request timed out","job_ids":{{jobIds}}}""");
 
        var result = await RunValidationScriptAsync(Path.Combine(_workspace.Path, "output.json"));
 
        Assert.NotEqual(0, result.ExitCode);
        Assert.Contains(
            "::error::Cause nuget-timeout.json contains unsupported or publisher-owned fields",
            result.Output,
            StringComparison.Ordinal);
    }
 
    [Theory]
    [InlineData(
        """
        {
          "verdict": "transient-infra",
          "failed_jobs": [
            {
              "id": 123,
              "name": "Forged job name",
              "classification": "transient-infra",
              "reason": "Request timed out"
            }
          ],
          "failed_tests": []
        }
        """)]
    [InlineData(
        """
        {
          "verdict": "transient-infra",
          "failed_jobs": [
            {
              "id": 123,
              "classification": "transient-infra",
              "reason": "Request timed out"
            }
          ],
          "failed_tests": []
        }
        """)]
    [RequiresTools(["bash", "jq"])]
    public async Task CommentRendererUsesTrustedFailedJobNames(string analysis)
    {
        var analysisPath = Path.Combine(_workspace.Path, "analysis.json");
        var trustedJobsPath = Path.Combine(_workspace.Path, "failed-jobs.json");
        await File.WriteAllTextAsync(analysisPath, analysis);
        await File.WriteAllTextAsync(
            trustedJobsPath,
            """[{"id":123,"name":"Build and Test (ubuntu-latest)"}]""");
 
        var result = await RunBashScriptAsync(
            Path.Combine(RepoRoot.Path, CommentScriptRelativePath),
            [analysisPath, trustedJobsPath, "https://github.com/microsoft/aspire/actions/runs/123"]);
 
        Assert.Equal(0, result.ExitCode);
        Assert.Equal(
            "- ` Build and Test (ubuntu-latest) ` — ` Request timed out ` (transient-infra)",
            Assert.Single(result.Output.Split('\n'), line => line.StartsWith("- `", StringComparison.Ordinal)));
    }
 
    [Theory]
    [InlineData("Forged job name", "- ` Tests.Flaky `")]
    [InlineData(
        "Build and Test (ubuntu-latest)",
        "- ` Tests.Flaky ` in job ` Build and Test (ubuntu-latest) `")]
    [RequiresTools(["bash", "jq"])]
    public async Task CommentRendererDisplaysOnlyTrustedFailedTestJobName(
        string reportedJobName,
        string expectedTestLine)
    {
        var analysisPath = Path.Combine(_workspace.Path, "analysis.json");
        var trustedJobsPath = Path.Combine(_workspace.Path, "failed-jobs.json");
        await File.WriteAllTextAsync(
            analysisPath,
            $$"""
            {
              "verdict": "flaky-test",
              "failed_jobs": [
                {
                  "id": 123,
                  "classification": "flaky-test",
                  "reason": "Known intermittent signature"
                }
              ],
              "failed_tests": [
                {
                  "name": "Tests.Flaky",
                  "job": "{{reportedJobName}}",
                  "error": "boom",
                  "stack_trace": "",
                  "classification": "flaky",
                  "reason": "Known intermittent signature"
                }
              ]
            }
            """);
        await File.WriteAllTextAsync(
            trustedJobsPath,
            """[{"id":123,"name":"Build and Test (ubuntu-latest)"}]""");
 
        var result = await RunBashScriptAsync(
            Path.Combine(RepoRoot.Path, CommentScriptRelativePath),
            [analysisPath, trustedJobsPath, "https://github.com/microsoft/aspire/actions/runs/123"]);
 
        Assert.Equal(0, result.ExitCode);
        Assert.Equal(
            expectedTestLine,
            Assert.Single(result.Output.Split('\n'), line => line.StartsWith("- ` Tests.Flaky `", StringComparison.Ordinal)));
    }
 
    [Theory]
    [InlineData("flaky-test")]
    [InlineData("mixed")]
    [RequiresTools(["bash", "jq"])]
    public async Task CommentRendererIncludesFailedJobsAndFlakyTestDetails(string verdict)
    {
        var analysisPath = Path.Combine(_workspace.Path, "analysis.json");
        var trustedJobsPath = Path.Combine(_workspace.Path, "failed-jobs.json");
        await File.WriteAllTextAsync(
            analysisPath,
            $$"""
            {
              "verdict": "{{verdict}}",
              "failed_jobs": [
                {
                  "id": 123,
                  "classification": "flaky-test",
                  "reason": "Known intermittent signature"
                },
                {
                  "id": 456,
                  "classification": "transient-infra",
                  "reason": "Runner disconnected"
                }
              ],
              "failed_tests": [
                {
                  "name": "Tests.Flaky",
                  "job": "Tests",
                  "error": "boom",
                  "stack_trace": "",
                  "standard_output": "stdout",
                  "standard_error": "stderr",
                  "classification": "flaky",
                  "reason": "Known intermittent signature"
                }
              ]
            }
            """);
        await File.WriteAllTextAsync(
            trustedJobsPath,
            """
            [
              {"id":123,"name":"Tests"},
              {"id":456,"name":"Infrastructure"}
            ]
            """);
 
        var result = await RunBashScriptAsync(
            Path.Combine(RepoRoot.Path, CommentScriptRelativePath),
            [analysisPath, trustedJobsPath, "https://github.com/microsoft/aspire/actions/runs/123"]);
 
        Assert.Equal(0, result.ExitCode);
        Assert.Collection(
            result.Output.Split('\n').Where(line => line.StartsWith("- `", StringComparison.Ordinal)),
            line => Assert.Equal("- ` Tests ` — ` Known intermittent signature ` (flaky-test)", line),
            line => Assert.Equal("- ` Infrastructure ` — ` Runner disconnected ` (transient-infra)", line),
            line => Assert.Equal("- ` Tests.Flaky ` in job ` Tests `", line));
        Assert.Contains("**Standard Output** (sensitive values redacted):", result.Output, StringComparison.Ordinal);
        Assert.Contains("        stdout", result.Output, StringComparison.Ordinal);
        Assert.Contains("**Standard Error** (sensitive values redacted):", result.Output, StringComparison.Ordinal);
        Assert.Contains("        stderr", result.Output, StringComparison.Ordinal);
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task AnalysisValidatorRejectsFlakyVerdictWithoutInfraCause()
    {
        await WriteValidationFixtureAsync(
            """
            {"run_id":123,"run_scope":"pull-request","verdict":"flaky-test","pr":{"number":42},
             "failed_jobs":[{"id":1,"classification":"flaky-test"},{"id":2,"classification":"transient-infra"}],
             "failed_tests":[{"name":"Tests.Flaky","job":"Tests","error":"boom","stack_trace":"","classification":"flaky","reason":"Intermittent"}],
             "causes":["flaky-failure"]}
            """,
            """{"run_id":123,"run_scope":"pull-request","pr_numbers":"42"}""",
            """[{"id":1,"name":"Tests"},{"id":2,"name":"Build"}]""",
            new Dictionary<string, string>
            {
                ["flaky-failure.json"] = CreateCause("flaky-failure", "flaky-test", 1),
            });
 
        await AssertValidationRejectsMismatchedCausePresenceAsync();
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task AnalysisValidatorRejectsCauseWithoutMatchingJob()
    {
        await WriteValidationFixtureAsync(
            """
            {"run_id":123,"run_scope":"pull-request","verdict":"flaky-test","pr":{"number":42},
             "failed_jobs":[{"id":1,"classification":"flaky-test"}],
             "failed_tests":[{"name":"Tests.Flaky","job":"Tests","error":"boom","stack_trace":"","classification":"flaky","reason":"Intermittent"}],
             "causes":["flaky-failure","infra-failure"]}
            """,
            """{"run_id":123,"run_scope":"pull-request","pr_numbers":"42"}""",
            """[{"id":1,"name":"Tests"}]""",
            new Dictionary<string, string>
            {
                ["flaky-failure.json"] = CreateCause("flaky-failure", "flaky-test", 1),
                ["infra-failure.json"] = CreateCause("infra-failure", "infra-failure", 1),
            });
 
        await AssertValidationRejectsIncompatibleCauseJobAsync();
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task AnalysisValidatorRejectsMainMixedVerdictMissingTransientCauseType()
    {
        await WriteValidationFixtureAsync(
            """
            {"run_id":123,"run_scope":"main","verdict":"mixed","pr":null,
             "failed_jobs":[
               {"id":1,"classification":"main-repository-breakage"},
               {"id":2,"classification":"flaky-test"},
               {"id":3,"classification":"transient-infra"}],
             "failed_tests":[{"name":"Tests.Flaky","job":"Tests","error":"boom","stack_trace":"","classification":"flaky","reason":"Intermittent"}],
             "causes":["main-failure","flaky-failure"]}
            """,
            """{"run_id":123,"run_scope":"main","pr_numbers":""}""",
            """[{"id":1,"name":"Build"},{"id":2,"name":"Tests"},{"id":3,"name":"Setup"}]""",
            new Dictionary<string, string>
            {
                ["main-failure.json"] = CreateCause("main-failure", "main-repository-breakage", 1),
                ["flaky-failure.json"] = CreateCause("flaky-failure", "flaky-test", 2),
            });
 
        await AssertValidationRejectsMismatchedCausePresenceAsync();
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task AnalysisValidatorRejectsPullRequestMixedVerdictWithWrongTransientCauseType()
    {
        await WriteValidationFixtureAsync(
            """
            {"run_id":123,"run_scope":"pull-request","verdict":"mixed","pr":{"number":42},
             "failed_jobs":[{"id":1,"classification":"code-issue"},{"id":2,"classification":"transient-infra"}],
             "failed_tests":[],
             "causes":["flaky-failure"]}
            """,
            """{"run_id":123,"run_scope":"pull-request","pr_numbers":"42"}""",
            """[{"id":1,"name":"Build"},{"id":2,"name":"Setup"}]""",
            new Dictionary<string, string>
            {
                ["flaky-failure.json"] = CreateCause("flaky-failure", "flaky-test", 2),
            });
 
        await AssertValidationRejectsIncompatibleCauseJobAsync();
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task AnalysisValidatorRejectsFlakyCauseForTransientInfraJobWithMatchingTestName()
    {
        // A flaky-test verdict permits a transient-infra job alongside the flaky-test job (both
        // count as "transient"). A flaky test sharing the transient-infra job's name must not
        // let a flaky-test cause cover that job: the cause contract only allows this
        // cross-reference for code-issue and main-repository-breakage jobs, never transient-infra.
        await WriteValidationFixtureAsync(
            """
            {"run_id":123,"run_scope":"pull-request","verdict":"flaky-test","pr":{"number":42},
             "failed_jobs":[{"id":1,"classification":"flaky-test"},{"id":2,"classification":"transient-infra"}],
             "failed_tests":[{"name":"Tests.Flaky","job":"Setup","error":"boom","stack_trace":"","classification":"flaky","reason":"Intermittent"}],
             "causes":["flaky-failure"]}
            """,
            """{"run_id":123,"run_scope":"pull-request","pr_numbers":"42"}""",
            """[{"id":1,"name":"Tests"},{"id":2,"name":"Setup"}]""",
            new Dictionary<string, string>
            {
                ["flaky-failure.json"] = CreateCause("flaky-failure", "flaky-test", 1, 2),
            });
 
        await AssertValidationRejectsIncompatibleCauseJobAsync();
    }
 
    [Theory]
    [InlineData("pull-request", "transient-infra", "infra-failure")]
    [InlineData("pull-request", "flaky-test", "flaky-test")]
    [InlineData("main", "main-repository-breakage", "main-repository-breakage")]
    [RequiresTools(["bash", "jq"])]
    public async Task AnalysisValidatorRejectsFailedJobWithoutMatchingCause(
        string runScope,
        string classification,
        string causeType)
    {
        var isMain = runScope == "main";
        var causeId = $"{causeType}-cause";
        var pr = isMain ? "null" : """{"number":42}""";
        await WriteValidationFixtureAsync(
            $$"""
            {"run_id":123,"run_scope":"{{runScope}}","verdict":"{{classification}}","pr":{{pr}},
             "failed_jobs":[
               {"id":1,"classification":"{{classification}}"},
               {"id":2,"classification":"{{classification}}"}],
             "failed_tests":[],
             "causes":["{{causeId}}"]}
            """,
            $$"""{"run_id":123,"run_scope":"{{runScope}}","pr_numbers":"{{(isMain ? "" : "42")}}"}""",
            """[{"id":1,"name":"Setup"},{"id":2,"name":"Build"}]""",
            $"{causeId}.json",
            CreateCause(causeId, causeType, 1));
 
        var result = await RunValidationScriptAsync(Path.Combine(_workspace.Path, "output.json"));
 
        Assert.NotEqual(0, result.ExitCode);
        Assert.Contains(
            "::error::Every transient, flaky, and main-breakage failed job must be covered by a matching cause",
            result.Output,
            StringComparison.Ordinal);
    }
 
    [Theory]
    [InlineData("main")]
    [InlineData("pull-request")]
    [RequiresTools(["bash", "jq"])]
    public async Task AnalysisValidatorAcceptsMixedVerdictWithMatchingCauseTypes(string runScope)
    {
        var isMain = runScope == "main";
        var analysis = isMain
            ? """
              {"run_id":123,"run_scope":"main","verdict":"mixed","pr":null,
               "failed_jobs":[
                 {"id":1,"classification":"main-repository-breakage"},
                 {"id":2,"classification":"flaky-test"},
                 {"id":3,"classification":"transient-infra"}],
               "failed_tests":[{"name":"Tests.Flaky","job":"Tests","error":"boom","stack_trace":"","classification":"flaky","reason":"Intermittent"}],
               "causes":["main-failure","flaky-failure","infra-failure"]}
              """
            : """
              {"run_id":123,"run_scope":"pull-request","verdict":"mixed","pr":{"number":42},
               "failed_jobs":[
                 {"id":1,"classification":"code-issue"},
                 {"id":2,"classification":"flaky-test"},
                 {"id":3,"classification":"transient-infra"}],
               "failed_tests":[{"name":"Tests.Flaky","job":"Tests","error":"boom","stack_trace":"","classification":"flaky","reason":"Intermittent"}],
               "causes":["flaky-failure","infra-failure"]}
              """;
        var causes = new Dictionary<string, string>
        {
            ["flaky-failure.json"] = CreateCause("flaky-failure", "flaky-test", 2),
            ["infra-failure.json"] = CreateCause("infra-failure", "infra-failure", 3),
        };
        if (isMain)
        {
            causes["main-failure.json"] = CreateCause("main-failure", "main-repository-breakage", 1);
        }
 
        await WriteValidationFixtureAsync(
            analysis,
            $$"""{"run_id":123,"run_scope":"{{runScope}}","pr_numbers":"{{(isMain ? "" : "42")}}"}""",
            """[{"id":1,"name":"Build"},{"id":2,"name":"Tests"},{"id":3,"name":"Setup"}]""",
            causes);
 
        var result = await RunValidationScriptAsync(Path.Combine(_workspace.Path, "output.json"));
 
        Assert.Equal(0, result.ExitCode);
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task AnalysisValidatorAcceptsPullRequestMixedVerdictWithinOneJob()
    {
        await WriteValidationFixtureAsync(
            """
            {"run_id":123,"run_scope":"pull-request","verdict":"mixed","pr":{"number":42},
             "failed_jobs":[{"id":1,"classification":"code-issue"}],
             "failed_tests":[{"name":"Tests.Flaky","job":"Tests","error":"boom","stack_trace":"","classification":"flaky","reason":"Intermittent"}],
             "causes":["flaky-failure"]}
            """,
            """{"run_id":123,"run_scope":"pull-request","pr_numbers":"42"}""",
            """[{"id":1,"name":"Tests"}]""",
            new Dictionary<string, string>
            {
                ["flaky-failure.json"] = CreateCause("flaky-failure", "flaky-test", 1),
            });
 
        var result = await RunValidationScriptAsync(Path.Combine(_workspace.Path, "output.json"));
 
        Assert.Equal(0, result.ExitCode);
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task AnalysisValidatorRejectsCodeIssueVerdictWithFlakyTest()
    {
        await WriteValidationFixtureAsync(
            """
            {"run_id":123,"run_scope":"pull-request","verdict":"code-issue","pr":{"number":42},
             "failed_jobs":[{"id":1,"classification":"code-issue"}],
             "failed_tests":[{"name":"Tests.Flaky","job":"Tests","error":"boom","stack_trace":"","classification":"flaky","reason":"Intermittent"}],
             "causes":[]}
            """,
            """{"run_id":123,"run_scope":"pull-request","pr_numbers":"42"}""",
            """[{"id":1,"name":"Tests"}]""");
 
        var result = await RunValidationScriptAsync(Path.Combine(_workspace.Path, "output.json"));
 
        Assert.NotEqual(0, result.ExitCode);
        Assert.Contains(
            "::error::Analysis failed_tests are incompatible with verdict code-issue",
            result.Output,
            StringComparison.Ordinal);
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task AnalysisValidatorAcceptsMainMixedVerdictWithinOneJob()
    {
        await WriteValidationFixtureAsync(
            """
            {"run_id":123,"run_scope":"main","verdict":"mixed","pr":null,
             "failed_jobs":[{"id":1,"classification":"main-repository-breakage"}],
             "failed_tests":[{"name":"Tests.Flaky","job":"Tests","error":"boom","stack_trace":"","classification":"flaky","reason":"Intermittent"}],
             "causes":["main-failure","flaky-failure"]}
            """,
            """{"run_id":123,"run_scope":"main","pr_numbers":""}""",
            """[{"id":1,"name":"Tests"}]""",
            new Dictionary<string, string>
            {
                ["main-failure.json"] = CreateCause("main-failure", "main-repository-breakage", 1),
                ["flaky-failure.json"] = CreateCause("flaky-failure", "flaky-test", 1),
            });
 
        var result = await RunValidationScriptAsync(Path.Combine(_workspace.Path, "output.json"));
 
        Assert.Equal(0, result.ExitCode);
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task AnalysisValidatorRejectsMainBreakageVerdictWithFlakyTest()
    {
        await WriteValidationFixtureAsync(
            """
            {"run_id":123,"run_scope":"main","verdict":"main-repository-breakage","pr":null,
             "failed_jobs":[{"id":1,"classification":"main-repository-breakage"}],
             "failed_tests":[{"name":"Tests.Flaky","job":"Tests","error":"boom","stack_trace":"","classification":"flaky","reason":"Intermittent"}],
             "causes":["main-failure"]}
            """,
            """{"run_id":123,"run_scope":"main","pr_numbers":""}""",
            """[{"id":1,"name":"Tests"}]""",
            "main-failure.json",
            CreateCause("main-failure", "main-repository-breakage", 1));
 
        var result = await RunValidationScriptAsync(Path.Combine(_workspace.Path, "output.json"));
 
        Assert.NotEqual(0, result.ExitCode);
        Assert.Contains(
            "::error::Analysis failed_tests are incompatible with verdict main-repository-breakage",
            result.Output,
            StringComparison.Ordinal);
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task MainRepositoryBreakageIssueUsesTrustedMainContext()
    {
        var causePath = Path.Combine(_workspace.Path, "main-build-break.json");
        var runContextPath = Path.Combine(_workspace.Path, "run-context.json");
        var lastSuccessfulRunPath = Path.Combine(_workspace.Path, "last-successful-main-run.json");
        var triggeringMergePath = Path.Combine(_workspace.Path, "triggering-merge-pr.json");
        var candidateHistoryStatusPath = Path.Combine(_workspace.Path, "candidate-merge-history-status.json");
        var bodyPath = Path.Combine(_workspace.Path, "issue-body.md");
        var metadataPath = Path.Combine(_workspace.Path, "issue-metadata.json");
        await File.WriteAllTextAsync(
            causePath,
            """{"id":"main-build-break","type":"main-repository-breakage","title":"PR #19999 broke main","error_pattern":"Introduced by PR #19999; revert it"}""");
        await File.WriteAllTextAsync(runContextPath, """{"head_sha":"trusted-failure"}""");
        await File.WriteAllTextAsync(lastSuccessfulRunPath, """{"head_sha":"trusted-success"}""");
        await File.WriteAllTextAsync(
            triggeringMergePath,
            """{"number":41,"title":"Candidate\r\n@reviewers [details](https://evil.example) `quoted`","html_url":"https://github.com/microsoft/aspire/pull/41"}""");
        await File.WriteAllTextAsync(candidateHistoryStatusPath, """{"state":"available"}""");
 
        var result = await RunBashScriptAsync(
            Path.Combine(RepoRoot.Path, IssueScriptRelativePath),
            [
                causePath,
                runContextPath,
                lastSuccessfulRunPath,
                triggeringMergePath,
                candidateHistoryStatusPath,
                "https://github.com/microsoft/aspire/actions/runs/123",
                "main",
                "0",
                "Build",
                "| 2026-08-31 | [123](https://github.com/microsoft/aspire/actions/runs/123) | Build | main |",
                bodyPath,
                metadataPath,
            ]);
 
        Assert.Equal(0, result.ExitCode);
        Assert.Empty(result.Output);
        using var metadata = JsonDocument.Parse(await File.ReadAllTextAsync(metadataPath));
        Assert.Equal(
            "[Main CI Failure] Main branch CI failure at trusted-failure",
            metadata.RootElement.GetProperty("title").GetString());
        Assert.Equal("ci-failure-cause,main-ci-break", metadata.RootElement.GetProperty("labels").GetString());
        Assert.Equal(
            """
            <!-- ci-failure-cause:main-build-break -->
            <!-- ci-failure-cause-type:main-repository-breakage -->
 
            ## Build Information
 
            Build: https://github.com/microsoft/aspire/actions/runs/123
            Affected branch: `main`
            Last successful main SHA: `trusted-success`
            Failed main SHA: `trusted-failure`
            Triggering merge PR (context only, not necessarily causal): #41 `` Candidate @reviewers [details](https://evil.example) `quoted` ``
 
            ## Error Message
 
                The main branch CI run failed. See the linked workflow run and trusted commit context above for diagnostics.
 
            ## Description
 
            ` Main branch CI failure at trusted-failure `
 
            **Type**: main-repository-breakage
 
            <!-- ci-failure-occurrences:start -->
            ## Occurrences
 
            Showing 1 most recent of 1 occurrences.
 
            | Date | Build | Job | Context |
            |------|-------|-----|----|
            | 2026-08-31 | [123](https://github.com/microsoft/aspire/actions/runs/123) | Build | main |
            <!-- ci-failure-occurrences:end -->
            """.ReplaceLineEndings("\n") + "\n",
            (await File.ReadAllTextAsync(bodyPath)).ReplaceLineEndings("\n"));
    }
 
    [Theory]
    [InlineData("unavailable")]
    [InlineData("incomplete")]
    [RequiresTools(["bash", "jq"])]
    public async Task MainRepositoryBreakageIssueOmitsTriggeringMergeWithoutCompleteHistory(string historyState)
    {
        var causePath = Path.Combine(_workspace.Path, "main-build-break.json");
        var runContextPath = Path.Combine(_workspace.Path, "run-context.json");
        var lastSuccessfulRunPath = Path.Combine(_workspace.Path, "last-successful-main-run.json");
        var triggeringMergePath = Path.Combine(_workspace.Path, "triggering-merge-pr.json");
        var candidateHistoryStatusPath = Path.Combine(_workspace.Path, "candidate-merge-history-status.json");
        var bodyPath = Path.Combine(_workspace.Path, "issue-body.md");
        var metadataPath = Path.Combine(_workspace.Path, "issue-metadata.json");
        await File.WriteAllTextAsync(
            causePath,
            """{"id":"main-build-break","type":"main-repository-breakage","title":"PR #19999 broke main","error_pattern":"Introduced by PR #19999; revert it"}""");
        await File.WriteAllTextAsync(runContextPath, """{"head_sha":"trusted-failure"}""");
        await File.WriteAllTextAsync(lastSuccessfulRunPath, """{"head_sha":"trusted-success"}""");
        await File.WriteAllTextAsync(
            triggeringMergePath,
            """{"number":41,"title":"Must not be published","html_url":"https://github.com/microsoft/aspire/pull/41"}""");
        await File.WriteAllTextAsync(
            candidateHistoryStatusPath,
            $$"""{"state":"{{historyState}}"}""");
 
        var result = await RunBashScriptAsync(
            Path.Combine(RepoRoot.Path, IssueScriptRelativePath),
            [
                causePath,
                runContextPath,
                lastSuccessfulRunPath,
                triggeringMergePath,
                candidateHistoryStatusPath,
                "https://github.com/microsoft/aspire/actions/runs/123",
                "main",
                "0",
                "Build",
                "| occurrence |",
                bodyPath,
                metadataPath,
            ]);
 
        Assert.Equal(0, result.ExitCode);
        using var metadata = JsonDocument.Parse(await File.ReadAllTextAsync(metadataPath));
        Assert.Equal(
            "[Main CI Failure] Main branch CI failure at trusted-failure",
            metadata.RootElement.GetProperty("title").GetString());
        Assert.Equal(
            """
            <!-- ci-failure-cause:main-build-break -->
            <!-- ci-failure-cause-type:main-repository-breakage -->
 
            ## Build Information
 
            Build: https://github.com/microsoft/aspire/actions/runs/123
            Affected branch: `main`
            Last successful main SHA: `trusted-success`
            Failed main SHA: `trusted-failure`
 
            ## Error Message
 
                The main branch CI run failed. See the linked workflow run and trusted commit context above for diagnostics.
 
            ## Description
 
            ` Main branch CI failure at trusted-failure `
 
            **Type**: main-repository-breakage
 
            <!-- ci-failure-occurrences:start -->
            ## Occurrences
 
            Showing 1 most recent of 1 occurrences.
 
            | Date | Build | Job | Context |
            |------|-------|-----|----|
            | occurrence |
            <!-- ci-failure-occurrences:end -->
            """.ReplaceLineEndings("\n") + "\n",
            (await File.ReadAllTextAsync(bodyPath)).ReplaceLineEndings("\n"));
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task IssueRendererUsesStoredOccurrenceTotalWhenRecreatingIssue()
    {
        var causePath = Path.Combine(_workspace.Path, "flaky-failure.json");
        var bodyPath = Path.Combine(_workspace.Path, "issue-body.md");
        var metadataPath = Path.Combine(_workspace.Path, "issue-metadata.json");
        await File.WriteAllTextAsync(
            causePath,
            """
            {
              "id":"flaky-failure",
              "type":"flaky-test",
              "title":"Flaky failure",
              "test_name":"Tests.Flaky",
              "error_pattern":"boom",
              "job_ids":[1],
              "occurrences":[{"run_id":1},{"run_id":2},{"run_id":3}]
            }
            """);
 
        var result = await RunBashScriptAsync(
            Path.Combine(RepoRoot.Path, IssueScriptRelativePath),
            [
                causePath,
                "unused-run-context.json",
                "unused-last-success.json",
                "unused-triggering-merge.json",
                "unused-history-status.json",
                "https://github.com/microsoft/aspire/actions/runs/3",
                "pull-request",
                "42",
                "Tests",
                "| current occurrence |",
                bodyPath,
                metadataPath,
            ]);
 
        Assert.Equal(0, result.ExitCode);
        Assert.Contains(
            "Showing 1 most recent of 3 occurrences.",
            await File.ReadAllTextAsync(bodyPath),
            StringComparison.Ordinal);
    }
 
    [Fact]
    [RequiresTools(["bash", "python3"])]
    public async Task TestResultsArtifactExtractionStreamsOnlyTrxFiles()
    {
        var archivePath = Path.Combine(_workspace.Path, "test-results.zip");
        using (var archive = ZipFile.Open(archivePath, ZipArchiveMode.Create))
        {
            await WriteZipEntryAsync(archive, "nested/results.trx", "<TestRun />");
            await WriteZipEntryAsync(archive, "ignored.txt", "ignored");
        }
        var outputDirectory = Path.Combine(_workspace.Path, "extracted");
 
        var result = await RunBashScriptAsync(
            Path.Combine(RepoRoot.Path, PersistenceScriptRelativePath),
            ["extract-test-results-artifact", archivePath, outputDirectory, "10", "1024"]);
 
        Assert.Equal(0, result.ExitCode);
        var extractedFile = Assert.Single(Directory.GetFiles(outputDirectory));
        Assert.Equal("00001.trx", Path.GetFileName(extractedFile));
        Assert.Equal("<TestRun />", await File.ReadAllTextAsync(extractedFile));
    }
 
    [Fact]
    [RequiresTools(["bash", "python3"])]
    public async Task TestResultsArtifactExtractionStreamsOnlyMochaResults()
    {
        var archivePath = Path.Combine(_workspace.Path, "test-results.zip");
        using (var archive = ZipFile.Open(archivePath, ZipArchiveMode.Create))
        {
            await WriteZipEntryAsync(archive, "extension/.test-results/debug/mocha.json", """{"failures":[]}""");
            await WriteZipEntryAsync(archive, "extension/.test-results/debug/metadata.json", """{"ignored":true}""");
        }
        var outputDirectory = Path.Combine(_workspace.Path, "extracted");
 
        var result = await RunBashScriptAsync(
            Path.Combine(RepoRoot.Path, PersistenceScriptRelativePath),
            [
                "extract-test-results-artifact",
                archivePath,
                outputDirectory,
                "10",
                "1024",
                "104857600",
                "",
                "mocha",
            ]);
 
        Assert.Equal(0, result.ExitCode);
        var extractedFile = Assert.Single(Directory.GetFiles(outputDirectory));
        Assert.Equal("00001.json", Path.GetFileName(extractedFile));
        Assert.Equal("""{"failures":[]}""", await File.ReadAllTextAsync(extractedFile));
    }
 
    [Fact]
    [RequiresTools(["bash", "python3"])]
    public async Task TestResultsArtifactExtractionRejectsWrittenBytesAboveBudget()
    {
        var archivePath = Path.Combine(_workspace.Path, "test-results.zip");
        using (var archive = ZipFile.Open(archivePath, ZipArchiveMode.Create))
        {
            await WriteZipEntryAsync(archive, "results.trx", new string('x', 11));
        }
        var outputDirectory = Path.Combine(_workspace.Path, "extracted");
 
        var result = await RunBashScriptAsync(
            Path.Combine(RepoRoot.Path, PersistenceScriptRelativePath),
            ["extract-test-results-artifact", archivePath, outputDirectory, "10", "10"]);
 
        Assert.NotEqual(0, result.ExitCode);
        Assert.Contains("uncompressed data exceeds the 10-byte budget", result.Output, StringComparison.Ordinal);
        Assert.False(Directory.Exists(outputDirectory));
    }
 
    [Fact]
    [RequiresTools(["bash", "python3"])]
    public async Task TestResultsArtifactExtractionRejectsDownloadedBytesAboveBudget()
    {
        var archivePath = Path.Combine(_workspace.Path, "test-results.zip");
        using (var archive = ZipFile.Open(archivePath, ZipArchiveMode.Create))
        {
            await WriteZipEntryAsync(archive, "results.trx", "<TestRun />");
        }
        var outputDirectory = Path.Combine(_workspace.Path, "extracted");
 
        var result = await RunBashScriptAsync(
            Path.Combine(RepoRoot.Path, PersistenceScriptRelativePath),
            ["extract-test-results-artifact", archivePath, outputDirectory, "10", "1024", "10"]);
 
        Assert.NotEqual(0, result.ExitCode);
        Assert.Contains("downloaded archive exceeds the 10-byte budget", result.Output, StringComparison.Ordinal);
        Assert.False(Directory.Exists(outputDirectory));
    }
 
    [Fact]
    [RequiresTools(["bash", "python3"])]
    public async Task TestResultsArtifactExtractionRejectsDownloadedSizeMismatch()
    {
        var archivePath = Path.Combine(_workspace.Path, "test-results.zip");
        using (var archive = ZipFile.Open(archivePath, ZipArchiveMode.Create))
        {
            await WriteZipEntryAsync(archive, "results.trx", "<TestRun />");
        }
        var outputDirectory = Path.Combine(_workspace.Path, "extracted");
        var expectedSize = new FileInfo(archivePath).Length + 1;
 
        var result = await RunBashScriptAsync(
            Path.Combine(RepoRoot.Path, PersistenceScriptRelativePath),
            [
                "extract-test-results-artifact",
                archivePath,
                outputDirectory,
                "10",
                "1024",
                "104857600",
                expectedSize.ToString(),
            ]);
 
        Assert.NotEqual(0, result.ExitCode);
        Assert.Contains(
            "downloaded archive size does not match artifact metadata",
            result.Output,
            StringComparison.Ordinal);
        Assert.False(Directory.Exists(outputDirectory));
    }
 
    [Fact]
    [RequiresTools(["bash", "python3"])]
    public async Task TestResultsArtifactExtractionRejectsExcessiveEntryCount()
    {
        var archivePath = Path.Combine(_workspace.Path, "test-results.zip");
        using (var archive = ZipFile.Open(archivePath, ZipArchiveMode.Create))
        {
            await WriteZipEntryAsync(archive, "first.trx", "first");
            await WriteZipEntryAsync(archive, "second.trx", "second");
        }
        var archiveBytes = await File.ReadAllBytesAsync(archivePath);
        var endRecordOffset = archiveBytes.AsSpan().LastIndexOf("PK\u0005\u0006"u8);
        Assert.True(endRecordOffset >= 0);
        BinaryPrimitives.WriteUInt16LittleEndian(archiveBytes.AsSpan(endRecordOffset + 8, 2), 1);
        BinaryPrimitives.WriteUInt16LittleEndian(archiveBytes.AsSpan(endRecordOffset + 10, 2), 1);
        await File.WriteAllBytesAsync(archivePath, archiveBytes);
        var outputDirectory = Path.Combine(_workspace.Path, "extracted");
 
        var result = await RunBashScriptAsync(
            Path.Combine(RepoRoot.Path, PersistenceScriptRelativePath),
            ["extract-test-results-artifact", archivePath, outputDirectory, "1", "1024"]);
 
        Assert.NotEqual(0, result.ExitCode);
        Assert.Contains("archive contains more than the 1-entry budget", result.Output, StringComparison.Ordinal);
        Assert.False(Directory.Exists(outputDirectory));
    }
 
    [Fact]
    [RequiresTools(["bash", "python3"])]
    public async Task TestResultsArtifactExtractionRejectsUnsafePaths()
    {
        var archivePath = Path.Combine(_workspace.Path, "test-results.zip");
        using (var archive = ZipFile.Open(archivePath, ZipArchiveMode.Create))
        {
            await WriteZipEntryAsync(archive, "../results.trx", "<TestRun />");
        }
        var outputDirectory = Path.Combine(_workspace.Path, "extracted");
 
        var result = await RunBashScriptAsync(
            Path.Combine(RepoRoot.Path, PersistenceScriptRelativePath),
            ["extract-test-results-artifact", archivePath, outputDirectory, "10", "1024"]);
 
        Assert.NotEqual(0, result.ExitCode);
        Assert.Contains("archive contains an unsafe path", result.Output, StringComparison.Ordinal);
        Assert.False(Directory.Exists(outputDirectory));
    }
 
    [Fact]
    [RequiresTools(["bash", "python3"])]
    public async Task TestResultsArtifactExtractionRejectsUnsupportedFileTypes()
    {
        var archivePath = Path.Combine(_workspace.Path, "test-results.zip");
        using (var archive = ZipFile.Open(archivePath, ZipArchiveMode.Create))
        {
            var entry = archive.CreateEntry("results.trx");
            entry.ExternalAttributes = unchecked((int)(0xA000u << 16));
            await using var stream = entry.Open();
            await using var writer = new StreamWriter(stream);
            await writer.WriteAsync("target.trx");
        }
        var outputDirectory = Path.Combine(_workspace.Path, "extracted");
 
        var result = await RunBashScriptAsync(
            Path.Combine(RepoRoot.Path, PersistenceScriptRelativePath),
            ["extract-test-results-artifact", archivePath, outputDirectory, "10", "1024"]);
 
        Assert.NotEqual(0, result.ExitCode);
        Assert.Contains("archive contains an unsupported file type", result.Output, StringComparison.Ordinal);
        Assert.False(Directory.Exists(outputDirectory));
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task TestResultArtifactSelectorBindsProducingJob()
    {
        var artifactsPath = Path.Combine(_workspace.Path, "artifacts.json");
        await File.WriteAllTextAsync(
            artifactsPath,
            """
            [
              {
                "id": 10,
                "name": "logs-Infrastructure-8-core-ubuntu-latest",
                "expired": false,
                "created_at": "2026-09-04T12:01:00Z",
                "size_in_bytes": 1024
              }
            ]
            """);
        var jobsPath = Path.Combine(_workspace.Path, "all-jobs.json");
        await File.WriteAllTextAsync(
            jobsPath,
            """
            [
              {
                "id":1,
                "name":"Tests / No-package tests / Infrastructure (8-core-ubuntu-latest)",
                "steps":[{"name":"Upload logs, and test results","conclusion":"success"}]
              },
              {"id":2,"name":"Tests / Build native CLI archive (Linux) / Build CLI (linux-x64)"}
            ]
            """);
 
        var result = await RunBashScriptAsync(
            Path.Combine(RepoRoot.Path, PersistenceScriptRelativePath),
            [
                "select-test-result-artifacts",
                artifactsPath,
                "2026-09-04T12:00:00Z",
                "2026-09-04T12:02:00Z",
                jobsPath,
            ]);
 
        Assert.Equal(0, result.ExitCode);
        Assert.Equal(
            """
            [{"id":10,"name":"logs-Infrastructure-8-core-ubuntu-latest","size_in_bytes":1024,"job":"Tests / No-package tests / Infrastructure (8-core-ubuntu-latest)","format":"trx"}]
            """,
            result.Output.Trim());
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task TestResultArtifactSelectorBindsExtensionE2eShardAndAttempt()
    {
        var artifactsPath = Path.Combine(_workspace.Path, "artifacts.json");
        await File.WriteAllTextAsync(
            artifactsPath,
            """
            [
              {
                "id": 10,
                "name": "extension-e2e-diagnostics-linux-x64-debug-attempt1",
                "expired": false,
                "created_at": "2026-09-04T12:01:00Z",
                "size_in_bytes": 1024
              },
              {
                "id": 20,
                "name": "extension-e2e-diagnostics-linux-x64-debug-attempt2",
                "expired": false,
                "created_at": "2026-09-04T12:01:00Z",
                "size_in_bytes": 2048
              }
            ]
            """);
        var jobsPath = Path.Combine(_workspace.Path, "all-jobs.json");
        await File.WriteAllTextAsync(
            jobsPath,
            """
            [
              {
                "id":1,
                "name":"Tests / VS Code extension E2E tests / VS Code extension E2E (Linux, debug)",
                "steps":[{"name":"Run extension E2E tests","conclusion":"failure"}]
              }
            ]
            """);
 
        var result = await RunBashScriptAsync(
            Path.Combine(RepoRoot.Path, PersistenceScriptRelativePath),
            [
                "select-test-result-artifacts",
                artifactsPath,
                "2026-09-04T12:00:00Z",
                "2026-09-04T12:02:00Z",
                jobsPath,
                "20",
                "1073741824",
                "104857600",
                "2",
            ]);
 
        Assert.Equal(0, result.ExitCode);
        Assert.Equal(
            """
            [{"id":20,"name":"extension-e2e-diagnostics-linux-x64-debug-attempt2","size_in_bytes":2048,"job":"Tests / VS Code extension E2E tests / VS Code extension E2E (Linux, debug)","format":"mocha"}]
            """,
            result.Output.Trim());
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task TestResultArtifactSelectorAllowsMissingOptionalExtensionDiagnostics()
    {
        var artifactsPath = Path.Combine(_workspace.Path, "artifacts.json");
        await File.WriteAllTextAsync(artifactsPath, "[]");
        var jobsPath = Path.Combine(_workspace.Path, "all-jobs.json");
        await File.WriteAllTextAsync(
            jobsPath,
            """
            [{
              "id":1,
              "name":"Run VS Code extension E2E tests / VS Code extension E2E (Linux, debug)",
              "steps":[{"name":"Install extension dependencies","conclusion":"failure"}]
            }]
            """);
 
        var result = await RunBashScriptAsync(
            Path.Combine(RepoRoot.Path, PersistenceScriptRelativePath),
            [
                "select-test-result-artifacts",
                artifactsPath,
                "2026-09-04T12:00:00Z",
                "2026-09-04T12:02:00Z",
                jobsPath,
            ]);
 
        Assert.Equal(0, result.ExitCode);
        Assert.Equal("[]", result.Output.Trim());
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task TestResultArtifactSelectorSkipsOversizedOptionalExtensionDiagnostics()
    {
        var artifactsPath = Path.Combine(_workspace.Path, "artifacts.json");
        await File.WriteAllTextAsync(
            artifactsPath,
            """
            [{
              "id":10,
              "name":"extension-e2e-diagnostics-linux-x64-debug-attempt1",
              "expired":false,
              "created_at":"2026-09-04T12:01:00Z",
              "size_in_bytes":104857601
            }]
            """);
        var jobsPath = Path.Combine(_workspace.Path, "all-jobs.json");
        await File.WriteAllTextAsync(
            jobsPath,
            """
            [{
              "id":1,
              "name":"Run VS Code extension E2E tests / VS Code extension E2E (Linux, debug)",
              "steps":[{"name":"Run extension E2E tests","conclusion":"failure"}]
            }]
            """);
 
        var result = await RunBashScriptAsync(
            Path.Combine(RepoRoot.Path, PersistenceScriptRelativePath),
            [
                "select-test-result-artifacts",
                artifactsPath,
                "2026-09-04T12:00:00Z",
                "2026-09-04T12:02:00Z",
                jobsPath,
            ]);
 
        Assert.Equal(0, result.ExitCode);
        Assert.Equal("[]", result.Output.Trim());
    }
 
    [Theory]
    [InlineData("2", "100")]
    [InlineData("3", "10")]
    [RequiresTools(["bash", "jq"])]
    public async Task TestResultArtifactSelectorKeepsRequiredEvidenceWhenOptionalArtifactsExceedSharedBudget(
        string maxArtifacts,
        string maxTotalBytes)
    {
        var artifactsPath = Path.Combine(_workspace.Path, "artifacts.json");
        await File.WriteAllTextAsync(
            artifactsPath,
            """
            [
              {
                "id":10,
                "name":"logs-Infrastructure-ubuntu-latest",
                "expired":false,
                "created_at":"2026-09-04T12:01:00Z",
                "size_in_bytes":4
              },
              {
                "id":20,
                "name":"extension-e2e-diagnostics-linux-x64-debug-attempt1",
                "expired":false,
                "created_at":"2026-09-04T12:01:00Z",
                "size_in_bytes":4
              },
              {
                "id":30,
                "name":"extension-e2e-diagnostics-linux-x64-command-palette-attempt1",
                "expired":false,
                "created_at":"2026-09-04T12:01:00Z",
                "size_in_bytes":4
              }
            ]
            """);
        var jobsPath = Path.Combine(_workspace.Path, "all-jobs.json");
        await File.WriteAllTextAsync(
            jobsPath,
            """
            [
              {
                "id":1,
                "name":"Tests / No-package tests / Infrastructure (ubuntu-latest)",
                "steps":[{"name":"Upload logs, and test results","conclusion":"success"}]
              },
              {
                "id":2,
                "name":"Run VS Code extension E2E tests / VS Code extension E2E (Linux, debug)"
              },
              {
                "id":3,
                "name":"Run VS Code extension E2E tests / VS Code extension E2E (Linux, command-palette)"
              }
            ]
            """);
 
        var result = await RunBashScriptAsync(
            Path.Combine(RepoRoot.Path, PersistenceScriptRelativePath),
            [
                "select-test-result-artifacts",
                artifactsPath,
                "2026-09-04T12:00:00Z",
                "2026-09-04T12:02:00Z",
                jobsPath,
                maxArtifacts,
                maxTotalBytes,
                "10",
                "1",
            ]);
 
        Assert.Equal(0, result.ExitCode);
        using var selected = JsonDocument.Parse(result.Output);
        Assert.Equal(2, selected.RootElement.GetArrayLength());
        Assert.Equal(10, selected.RootElement[0].GetProperty("id").GetInt32());
        Assert.Equal("trx", selected.RootElement[0].GetProperty("format").GetString());
        Assert.Equal("mocha", selected.RootElement[1].GetProperty("format").GetString());
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task TestResultArtifactSelectorRejectsMissingArtifactForRecognizedTestJob()
    {
        var artifactsPath = Path.Combine(_workspace.Path, "artifacts.json");
        await File.WriteAllTextAsync(artifactsPath, "[]");
        var jobsPath = Path.Combine(_workspace.Path, "all-jobs.json");
        await File.WriteAllTextAsync(
            jobsPath,
            """
            [
              {
                "id":1,
                "name":"Tests / No-package tests / Infrastructure (8-core-ubuntu-latest)",
                "steps":[{"name":"Upload logs, and test results","conclusion":"success"}]
              }
            ]
            """);
 
        var result = await RunBashScriptAsync(
            Path.Combine(RepoRoot.Path, PersistenceScriptRelativePath),
            [
                "select-test-result-artifacts",
                artifactsPath,
                "2026-09-04T12:00:00Z",
                "2026-09-04T12:02:00Z",
                jobsPath,
            ]);
 
        Assert.NotEqual(0, result.ExitCode);
        Assert.Contains(
            "test result artifact is missing for a failed test job",
            result.Output,
            StringComparison.Ordinal);
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task TestResultArtifactSelectorRejectsRecognizedTestJobWithMalformedName()
    {
        var artifactsPath = Path.Combine(_workspace.Path, "artifacts.json");
        await File.WriteAllTextAsync(artifactsPath, "[]");
        var jobsPath = Path.Combine(_workspace.Path, "all-jobs.json");
        await File.WriteAllTextAsync(
            jobsPath,
            """
            [
              {
                "id":1,
                "name":"Tests / malformed",
                "steps":[{"name":"Upload logs, and test results","conclusion":"success"}]
              }
            ]
            """);
 
        var result = await RunBashScriptAsync(
            Path.Combine(RepoRoot.Path, PersistenceScriptRelativePath),
            [
                "select-test-result-artifacts",
                artifactsPath,
                "2026-09-04T12:00:00Z",
                "2026-09-04T12:02:00Z",
                jobsPath,
            ]);
 
        Assert.NotEqual(0, result.ExitCode);
        Assert.Contains(
            "failed test job name does not match the artifact naming contract",
            result.Output,
            StringComparison.Ordinal);
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task TestResultArtifactSelectorRecognizesTestJobBeforeUploadStepStarts()
    {
        var artifactsPath = Path.Combine(_workspace.Path, "artifacts.json");
        await File.WriteAllTextAsync(artifactsPath, "[]");
        var jobsPath = Path.Combine(_workspace.Path, "all-jobs.json");
        await File.WriteAllTextAsync(
            jobsPath,
            """
            [
              {
                "id":1,
                "name":"Tests / No-package tests (regular, Aspire.Hosting.Docker.Tests, Hosting.Docker, Hosting.Docker, tests/Asp... / Hosting.Docker (ubuntu-latest)",
                "steps":[{"name":"Checkout code","conclusion":"failure"}]
              }
            ]
            """);
 
        var result = await RunBashScriptAsync(
            Path.Combine(RepoRoot.Path, PersistenceScriptRelativePath),
            [
                "select-test-result-artifacts",
                artifactsPath,
                "2026-09-04T12:00:00Z",
                "2026-09-04T12:02:00Z",
                jobsPath,
            ]);
 
        Assert.NotEqual(0, result.ExitCode);
        Assert.Contains(
            "test result artifact is missing for a failed test job",
            result.Output,
            StringComparison.Ordinal);
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task TestResultArtifactSelectorRejectsAmbiguousProducingJobs()
    {
        var artifactsPath = Path.Combine(_workspace.Path, "artifacts.json");
        await File.WriteAllTextAsync(
            artifactsPath,
            """
            [
              {
                "id": 10,
                "name": "logs-Infrastructure-ubuntu-latest",
                "expired": false,
                "created_at": "2026-09-04T12:01:00Z",
                "size_in_bytes": 1024
              }
            ]
            """);
        var jobsPath = Path.Combine(_workspace.Path, "all-jobs.json");
        await File.WriteAllTextAsync(
            jobsPath,
            """
            [
              {
                "id":1,
                "name":"Tests / No-package tests / Infrastructure (ubuntu-latest)",
                "steps":[{"name":"Upload logs, and test results","conclusion":"success"}]
              },
              {
                "id":2,
                "name":"Tests / No-package tests / Infrastructure (ubuntu-latest)",
                "steps":[{"name":"Upload logs, and test results","conclusion":"success"}]
              }
            ]
            """);
 
        var result = await RunBashScriptAsync(
            Path.Combine(RepoRoot.Path, PersistenceScriptRelativePath),
            [
                "select-test-result-artifacts",
                artifactsPath,
                "2026-09-04T12:00:00Z",
                "2026-09-04T12:02:00Z",
                jobsPath,
            ]);
 
        Assert.NotEqual(0, result.ExitCode);
        Assert.Contains(
            "test result artifact does not identify exactly one failed job",
            result.Output,
            StringComparison.Ordinal);
    }
 
    [Fact]
    [RequiresTools(["bash", "jq", "yq"])]
    public async Task TrustedTestFailureCollectorBindsProducingJob()
    {
        var testResultsDirectory = Directory.CreateDirectory(Path.Combine(_workspace.Path, "test-results"));
        await File.WriteAllTextAsync(
            Path.Combine(testResultsDirectory.FullName, "00001.trx"),
            """
            <TestRun>
              <Results>
                <UnitTestResult testName="Tests.Failed" outcome="Failed">
                  <Output>
                    <ErrorInfo>
                      <Message>boom</Message>
                      <StackTrace>frame</StackTrace>
                    </ErrorInfo>
                    <StdOut>stdout</StdOut>
                    <StdErr>stderr</StdErr>
                  </Output>
                </UnitTestResult>
              </Results>
            </TestRun>
            """);
        var jobsPath = Path.Combine(_workspace.Path, "all-jobs.json");
        await File.WriteAllTextAsync(
            jobsPath,
            """
            [{"id":1,"name":"Tests / No-package tests / Infrastructure (8-core-ubuntu-latest)"}]
            """);
        var outputPath = Path.Combine(_workspace.Path, "test-failures.json");
 
        var result = await RunBashScriptAsync(
            Path.Combine(RepoRoot.Path, PersistenceScriptRelativePath),
            [
                "collect-test-failures",
                testResultsDirectory.FullName,
                "Tests / No-package tests / Infrastructure (8-core-ubuntu-latest)",
                jobsPath,
                outputPath,
            ]);
 
        Assert.Equal(0, result.ExitCode);
        Assert.Equal(
            """
            [{"test":"Tests.Failed","job":"Tests / No-package tests / Infrastructure (8-core-ubuntu-latest)","error":"boom","stack_trace":"frame","standard_output":"stdout","standard_error":"stderr"}]
            """,
            (await File.ReadAllTextAsync(outputPath)).Trim());
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task TrustedTestFailureCollectorReadsCompletedMochaFailures()
    {
        var testResultsDirectory = Directory.CreateDirectory(Path.Combine(_workspace.Path, "test-results"));
        await File.WriteAllTextAsync(
            Path.Combine(testResultsDirectory.FullName, "00001.json"),
            """
            {
              "tests": [{"fullTitle":"suite fails"}],
              "failures": [{
                "fullTitle":"suite fails",
                "err":{"name":"AssertionError","message":"expected ConnectionString=secret","stack":"frame"}
              }]
            }
            """);
        var jobName = "Tests / VS Code extension E2E tests / VS Code extension E2E (Linux, debug)";
        var jobsPath = Path.Combine(_workspace.Path, "all-jobs.json");
        await File.WriteAllTextAsync(
            jobsPath,
            $$"""[{"id":1,"name":{{JsonSerializer.Serialize(jobName)}}}]""");
        var outputPath = Path.Combine(_workspace.Path, "test-failures.json");
 
        var result = await RunBashScriptAsync(
            Path.Combine(RepoRoot.Path, PersistenceScriptRelativePath),
            [
                "collect-test-failures",
                testResultsDirectory.FullName,
                jobName,
                jobsPath,
                outputPath,
                "mocha",
            ]);
 
        Assert.Equal(0, result.ExitCode);
        Assert.Equal(
            $$"""
            [{"test":"suite fails","job":{{JsonSerializer.Serialize(jobName)}},"error":"expected ConnectionString=[REDACTED]","stack_trace":"frame","standard_output":"","standard_error":""}]
            """,
            (await File.ReadAllTextAsync(outputPath)).Trim());
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task TrustedTestFailureCollectorExcludesMochaHarnessFailures()
    {
        var testResultsDirectory = Directory.CreateDirectory(Path.Combine(_workspace.Path, "test-results"));
        await File.WriteAllTextAsync(
            Path.Combine(testResultsDirectory.FullName, "00001.json"),
            """
            {
              "tests": [{"fullTitle":"suite fails"}],
              "failures": [{
                "fullTitle":"suite fails",
                "err":{"name":"NoSuchSessionError","message":"browser crashed","stack":"frame"}
              }]
            }
            """);
        var jobName = "Tests / VS Code extension E2E tests / VS Code extension E2E (Linux, debug)";
        var jobsPath = Path.Combine(_workspace.Path, "all-jobs.json");
        await File.WriteAllTextAsync(
            jobsPath,
            $$"""[{"id":1,"name":{{JsonSerializer.Serialize(jobName)}}}]""");
        var outputPath = Path.Combine(_workspace.Path, "test-failures.json");
 
        var result = await RunBashScriptAsync(
            Path.Combine(RepoRoot.Path, PersistenceScriptRelativePath),
            [
                "collect-test-failures",
                testResultsDirectory.FullName,
                jobName,
                jobsPath,
                outputPath,
                "mocha",
            ]);
 
        Assert.Equal(0, result.ExitCode);
        Assert.Equal("[]", (await File.ReadAllTextAsync(outputPath)).Trim());
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task TrustedTestFailureCollectorDoesNotPartiallyTrustMixedMochaFailures()
    {
        var testResultsDirectory = Directory.CreateDirectory(Path.Combine(_workspace.Path, "test-results"));
        await File.WriteAllTextAsync(
            Path.Combine(testResultsDirectory.FullName, "00001.json"),
            """
            {
              "tests": [{"fullTitle":"suite assertion"}],
              "failures": [
                {
                  "fullTitle":"suite assertion",
                  "err":{"name":"AssertionError","message":"expected true","stack":"frame"}
                },
                {
                  "fullTitle":"after each hook",
                  "err":{"name":"NoSuchSessionError","message":"browser crashed","stack":"frame"}
                }
              ]
            }
            """);
        var jobName = "Run VS Code extension E2E tests / VS Code extension E2E (Linux, debug)";
        var jobsPath = Path.Combine(_workspace.Path, "all-jobs.json");
        await File.WriteAllTextAsync(
            jobsPath,
            $$"""[{"id":1,"name":{{JsonSerializer.Serialize(jobName)}}}]""");
        var outputPath = Path.Combine(_workspace.Path, "test-failures.json");
 
        var result = await RunBashScriptAsync(
            Path.Combine(RepoRoot.Path, PersistenceScriptRelativePath),
            [
                "collect-test-failures",
                testResultsDirectory.FullName,
                jobName,
                jobsPath,
                outputPath,
                "mocha",
            ]);
 
        Assert.Equal(0, result.ExitCode);
        Assert.Equal("[]", (await File.ReadAllTextAsync(outputPath)).Trim());
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task TrustedTestFailureCollectorAllowsMissingMochaResult()
    {
        var testResultsDirectory = Directory.CreateDirectory(Path.Combine(_workspace.Path, "test-results"));
        var jobName = "Run VS Code extension E2E tests / VS Code extension E2E (Linux, debug)";
        var jobsPath = Path.Combine(_workspace.Path, "all-jobs.json");
        await File.WriteAllTextAsync(
            jobsPath,
            $$"""[{"id":1,"name":{{JsonSerializer.Serialize(jobName)}}}]""");
        var outputPath = Path.Combine(_workspace.Path, "test-failures.json");
 
        var result = await RunBashScriptAsync(
            Path.Combine(RepoRoot.Path, PersistenceScriptRelativePath),
            [
                "collect-test-failures",
                testResultsDirectory.FullName,
                jobName,
                jobsPath,
                outputPath,
                "mocha",
            ]);
 
        Assert.Equal(0, result.ExitCode);
        Assert.Equal("[]", (await File.ReadAllTextAsync(outputPath)).Trim());
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task TrustedTestFailureCollectorRejectsMalformedMochaReport()
    {
        var testResultsDirectory = Directory.CreateDirectory(Path.Combine(_workspace.Path, "test-results"));
        await File.WriteAllTextAsync(
            Path.Combine(testResultsDirectory.FullName, "00001.json"),
            """{"tests":[]}""");
        var jobName = "Tests / VS Code extension E2E tests / VS Code extension E2E (Linux, debug)";
        var jobsPath = Path.Combine(_workspace.Path, "all-jobs.json");
        await File.WriteAllTextAsync(
            jobsPath,
            $$"""[{"id":1,"name":{{JsonSerializer.Serialize(jobName)}}}]""");
        var outputPath = Path.Combine(_workspace.Path, "test-failures.json");
 
        var result = await RunBashScriptAsync(
            Path.Combine(RepoRoot.Path, PersistenceScriptRelativePath),
            [
                "collect-test-failures",
                testResultsDirectory.FullName,
                jobName,
                jobsPath,
                outputPath,
                "mocha",
            ]);
 
        Assert.NotEqual(0, result.ExitCode);
        Assert.False(File.Exists(outputPath));
        Assert.Contains(
            "::error::Unable to parse extracted test result 00001.json",
            result.Output,
            StringComparison.Ordinal);
    }
 
    [Fact]
    [RequiresTools(["bash", "jq", "yq"])]
    public async Task TrustedTestFailureCollectorRejectsPartialEvidenceWhenAnyTrxIsMalformed()
    {
        var testResultsDirectory = Directory.CreateDirectory(Path.Combine(_workspace.Path, "test-results"));
        await File.WriteAllTextAsync(
            Path.Combine(testResultsDirectory.FullName, "00001.trx"),
            """
            <TestRun>
              <Results>
                <UnitTestResult testName="Tests.Failed" outcome="Failed" />
              </Results>
            </TestRun>
            """);
        await File.WriteAllTextAsync(
            Path.Combine(testResultsDirectory.FullName, "00002.trx"),
            "<TestRun><");
        var jobsPath = Path.Combine(_workspace.Path, "all-jobs.json");
        await File.WriteAllTextAsync(
            jobsPath,
            """[{"id":1,"name":"Tests / No-package tests / Infrastructure (ubuntu-latest)"}]""");
        var outputPath = Path.Combine(_workspace.Path, "test-failures.json");
 
        var result = await RunBashScriptAsync(
            Path.Combine(RepoRoot.Path, PersistenceScriptRelativePath),
            [
                "collect-test-failures",
                testResultsDirectory.FullName,
                "Tests / No-package tests / Infrastructure (ubuntu-latest)",
                jobsPath,
                outputPath,
            ]);
 
        Assert.NotEqual(0, result.ExitCode);
        Assert.False(File.Exists(outputPath));
        Assert.Contains(
            "::error::Unable to parse extracted test result 00002.trx",
            result.Output,
            StringComparison.Ordinal);
    }
 
    [Fact]
    [RequiresTools(["bash", "jq", "yq"])]
    public async Task TrustedTestFailureCollectorRejectsWellFormedXmlThatIsNotTrx()
    {
        var testResultsDirectory = Directory.CreateDirectory(Path.Combine(_workspace.Path, "test-results"));
        await File.WriteAllTextAsync(
            Path.Combine(testResultsDirectory.FullName, "00001.trx"),
            "<NotATrx />");
        var jobsPath = Path.Combine(_workspace.Path, "all-jobs.json");
        await File.WriteAllTextAsync(
            jobsPath,
            """[{"id":1,"name":"Tests / No-package tests / Infrastructure (ubuntu-latest)"}]""");
        var outputPath = Path.Combine(_workspace.Path, "test-failures.json");
 
        var result = await RunBashScriptAsync(
            Path.Combine(RepoRoot.Path, PersistenceScriptRelativePath),
            [
                "collect-test-failures",
                testResultsDirectory.FullName,
                "Tests / No-package tests / Infrastructure (ubuntu-latest)",
                jobsPath,
                outputPath,
            ]);
 
        Assert.NotEqual(0, result.ExitCode);
        Assert.False(File.Exists(outputPath));
        Assert.Contains(
            "::error::Unable to parse extracted test result 00001.trx",
            result.Output,
            StringComparison.Ordinal);
    }
 
    [Fact]
    [RequiresTools(["bash", "jq", "yq"])]
    public async Task TrustedTestFailureCollectorRejectsArtifactWithoutTrx()
    {
        var testResultsDirectory = Directory.CreateDirectory(Path.Combine(_workspace.Path, "test-results"));
        await File.WriteAllTextAsync(Path.Combine(testResultsDirectory.FullName, "results.xml"), "<TestRun />");
        var jobsPath = Path.Combine(_workspace.Path, "all-jobs.json");
        await File.WriteAllTextAsync(
            jobsPath,
            """[{"id":1,"name":"Tests / No-package tests / Infrastructure (ubuntu-latest)"}]""");
        var outputPath = Path.Combine(_workspace.Path, "test-failures.json");
 
        var result = await RunBashScriptAsync(
            Path.Combine(RepoRoot.Path, PersistenceScriptRelativePath),
            [
                "collect-test-failures",
                testResultsDirectory.FullName,
                "Tests / No-package tests / Infrastructure (ubuntu-latest)",
                jobsPath,
                outputPath,
            ]);
 
        Assert.NotEqual(0, result.ExitCode);
        Assert.False(File.Exists(outputPath));
        Assert.Contains(
            "::error::Selected test result artifact does not contain any TRX files",
            result.Output,
            StringComparison.Ordinal);
    }
 
    [Fact]
    [RequiresTools(["bash", "jq", "yq"])]
    public async Task TrustedTestFailureCollectorRejectsUnboundJobEvidence()
    {
        var testResultsDirectory = Directory.CreateDirectory(Path.Combine(_workspace.Path, "test-results"));
        await File.WriteAllTextAsync(
            Path.Combine(testResultsDirectory.FullName, "00001.trx"),
            """
            <TestRun>
              <Results>
                <UnitTestResult testName="Tests.Failed" outcome="Failed" />
              </Results>
            </TestRun>
            """);
        var jobsPath = Path.Combine(_workspace.Path, "all-jobs.json");
        await File.WriteAllTextAsync(
            jobsPath,
            """[{"id":1,"name":"Tests / No-package tests / Infrastructure (ubuntu-latest)"}]""");
        var outputPath = Path.Combine(_workspace.Path, "test-failures.json");
 
        var result = await RunBashScriptAsync(
            Path.Combine(RepoRoot.Path, PersistenceScriptRelativePath),
            [
                "collect-test-failures",
                testResultsDirectory.FullName,
                "Tests / No-package tests / Unknown (ubuntu-latest)",
                jobsPath,
                outputPath,
            ]);
 
        Assert.NotEqual(0, result.ExitCode);
        Assert.False(File.Exists(outputPath));
        Assert.Contains(
            "::error::Trusted test result provenance is invalid",
            result.Output,
            StringComparison.Ordinal);
    }
 
    [Theory]
    [InlineData("open")]
    [InlineData("closed")]
    [RequiresTools(["bash", "jq"])]
    public async Task CauseIssueCacheFailsWhenEitherIssueLookupFails(string failingState)
    {
        var fakeGhPath = await CreateFakeGhAsync(
            """
            #!/usr/bin/env bash
            if [[ "$*" == *"-f state=${FAILING_STATE}"* ]]; then
              echo "lookup failed" >&2
              exit 1
            fi
            echo '[]'
            """);
        var openIssuesPath = Path.Combine(_workspace.Path, "open-issues.json");
        var closedIssuesPath = Path.Combine(_workspace.Path, "closed-issues.json");
        await File.WriteAllTextAsync(openIssuesPath, "stale");
        await File.WriteAllTextAsync(closedIssuesPath, "stale");
 
        var result = await RunBashScriptAsync(
            Path.Combine(RepoRoot.Path, PersistenceScriptRelativePath),
            ["cache-cause-issues", "microsoft/aspire", openIssuesPath, closedIssuesPath],
            new Dictionary<string, string>
            {
                ["FAILING_STATE"] = failingState,
                ["PATH"] = $"{Path.GetDirectoryName(fakeGhPath)}{Path.PathSeparator}{Environment.GetEnvironmentVariable("PATH")}",
            });
 
        Assert.NotEqual(0, result.ExitCode);
        Assert.Contains($"Failed to load {failingState} cause issues", result.Output, StringComparison.Ordinal);
        Assert.False(File.Exists(openIssuesPath));
        Assert.False(File.Exists(closedIssuesPath));
    }
 
    [Theory]
    [InlineData("""{"state":"open","locked":false}""", 0, "true")]
    [InlineData("""{"state":"closed","locked":false}""", 0, "false")]
    [InlineData("""{"state":"open","locked":true}""", 0, "false")]
    [InlineData("", 1, "")]
    [InlineData("", 0, "")]
    [InlineData("{}", 0, "")]
    [InlineData("""{"state":"open","locked":"false"}""", 0, "")]
    [InlineData("""{"state":1,"locked":false}""", 0, "")]
    [RequiresTools(["bash", "jq"])]
    public async Task PrActionableLookupRequiresOpenUnlockedResponse(
        string response,
        int ghExitCode,
        string expectedOutput)
    {
        var fakeGhPath = await CreateFakeGhAsync(
            """
            #!/usr/bin/env bash
            printf '%s' "${GH_RESPONSE}"
            exit "${GH_EXIT_CODE}"
            """);
 
        var result = await RunBashScriptAsync(
            Path.Combine(RepoRoot.Path, PersistenceScriptRelativePath),
            ["pr-actionable", "microsoft/aspire", "42"],
            new Dictionary<string, string>
            {
                ["GH_EXIT_CODE"] = ghExitCode.ToString(),
                ["GH_RESPONSE"] = response,
                ["PATH"] = $"{Path.GetDirectoryName(fakeGhPath)}{Path.PathSeparator}{Environment.GetEnvironmentVariable("PATH")}",
            });
 
        if (expectedOutput.Length == 0)
        {
            Assert.NotEqual(0, result.ExitCode);
            Assert.Contains("Unable to determine whether PR #42 is actionable", result.Output, StringComparison.Ordinal);
        }
        else
        {
            Assert.Equal(0, result.ExitCode);
            Assert.Equal(expectedOutput, result.Output.Trim());
        }
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task ExistingAnalysisCommentLookupSurfacesApiFailure()
    {
        var fakeGhPath = await CreateFakeGhAsync("#!/usr/bin/env bash\nexit 1");
 
        var result = await RunBashScriptAsync(
            Path.Combine(RepoRoot.Path, PersistenceScriptRelativePath),
            ["find-analysis-comment", "microsoft/aspire", "42"],
            new Dictionary<string, string>
            {
                ["PATH"] = $"{Path.GetDirectoryName(fakeGhPath)}{Path.PathSeparator}{Environment.GetEnvironmentVariable("PATH")}",
            });
 
        Assert.NotEqual(0, result.ExitCode);
        Assert.Contains("Failed to list existing analysis comments", result.Output, StringComparison.Ordinal);
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task ExistingAnalysisCommentLookupReturnsFirstMatchWithoutPipeFailure()
    {
        var fakeGhPath = await CreateFakeGhAsync(
            """
            #!/usr/bin/env bash
            seq 1 100000
            """);
 
        var result = await RunBashScriptAsync(
            Path.Combine(RepoRoot.Path, PersistenceScriptRelativePath),
            ["find-analysis-comment", "microsoft/aspire", "42"],
            new Dictionary<string, string>
            {
                ["PATH"] = $"{Path.GetDirectoryName(fakeGhPath)}{Path.PathSeparator}{Environment.GetEnvironmentVariable("PATH")}",
            });
 
        Assert.Equal(0, result.ExitCode);
        Assert.Equal("1", result.Output.Trim());
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task CauseIssueCachePaginatesAndExcludesPullRequests()
    {
        var callLogPath = Path.Combine(_workspace.Path, "gh-calls.log");
        var fakeGhPath = await CreateFakeGhAsync(
            """
            #!/usr/bin/env bash
            echo "$*" >> "${GH_CALL_LOG}"
            case "$*" in
              *"-f state=open"*) echo '[[{"number":1,"body":"open"},{"number":99,"body":"pr","pull_request":{}}],[{"number":3,"body":"second page"}]]' ;;
              *"-f state=closed"*) echo '[[{"number":2,"body":"closed"},{"number":98,"body":"pr","pull_request":{}}],[{"number":4,"body":"second closed page"}]]' ;;
              *) exit 99 ;;
            esac
            """);
        var openIssuesPath = Path.Combine(_workspace.Path, "open-issues.json");
        var closedIssuesPath = Path.Combine(_workspace.Path, "closed-issues.json");
 
        var result = await RunBashScriptAsync(
            Path.Combine(RepoRoot.Path, PersistenceScriptRelativePath),
            ["cache-cause-issues", "microsoft/aspire", openIssuesPath, closedIssuesPath],
            new Dictionary<string, string>
            {
                ["GH_CALL_LOG"] = callLogPath,
                ["PATH"] = $"{Path.GetDirectoryName(fakeGhPath)}{Path.PathSeparator}{Environment.GetEnvironmentVariable("PATH")}",
            });
 
        Assert.Equal(0, result.ExitCode);
        Assert.Equal("""[{"number":1,"body":"open"},{"number":3,"body":"second page"}]""" + Environment.NewLine, await File.ReadAllTextAsync(openIssuesPath));
        Assert.Equal("""[{"number":2,"body":"closed"},{"number":4,"body":"second closed page"}]""" + Environment.NewLine, await File.ReadAllTextAsync(closedIssuesPath));
        Assert.All(
            await File.ReadAllLinesAsync(callLogPath),
            call => Assert.Contains("api --method GET --paginate --slurp repos/microsoft/aspire/issues", call, StringComparison.Ordinal));
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task LastSuccessfulMainRunUsesExplicitOrderingForShuffledResults()
    {
        var fakeGh = """
            #!/usr/bin/env bash
            echo "$*" >> "${GH_CALL_LOG}"
            cat <<'JSON'
            {
              "total_count": 3,
              "workflow_runs": [
                {"id": 30, "created_at": "2026-08-30T11:00:00Z", "head_sha": "after"},
                {"id": 20, "created_at": "2026-08-30T09:00:00Z", "head_sha": "latest"},
                {"id": 20, "created_at": "2026-08-30T09:00:00Z", "head_sha": "latest"},
                {"id": 10, "created_at": "2026-08-30T08:00:00Z", "head_sha": "older"}
              ]
            }
            JSON
            """;
 
        var result = await RunHistoryScriptAsync(
            fakeGh,
            "2026-08-30T10:00:00Z",
            Path.Combine(_workspace.Path, "last-success.json"));
 
        Assert.Equal(0, result.ExitCode);
        using var output = JsonDocument.Parse(await File.ReadAllTextAsync(Path.Combine(_workspace.Path, "last-success.json")));
        Assert.Equal(20, output.RootElement.GetProperty("id").GetInt64());
        Assert.Equal("latest", output.RootElement.GetProperty("head_sha").GetString());
        Assert.Contains("per_page=100", await File.ReadAllTextAsync(Path.Combine(_workspace.Path, "gh-calls.log")), StringComparison.Ordinal);
        Assert.All(
            await File.ReadAllLinesAsync(Path.Combine(_workspace.Path, "gh-calls.log")),
            call =>
            {
                Assert.Contains("branch=main", call, StringComparison.Ordinal);
                Assert.Contains("event=push", call, StringComparison.Ordinal);
                Assert.Contains("status=success", call, StringComparison.Ordinal);
            });
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task LastSuccessfulMainRunOrdersRunsCreatedInTheSameSecond()
    {
        var fakeGh = """
            #!/usr/bin/env bash
            cat <<'JSON'
            {
              "total_count": 3,
              "workflow_runs": [
                {"id": 26, "created_at": "2026-08-30T10:00:00Z", "head_sha": "later"},
                {"id": 24, "created_at": "2026-08-30T10:00:00Z", "head_sha": "earlier"},
                {"id": 20, "created_at": "2026-08-30T09:00:00Z", "head_sha": "older"}
              ]
            }
            JSON
            """;
        var outputPath = Path.Combine(_workspace.Path, "last-success.json");
 
        var result = await RunHistoryScriptAsync(
            fakeGh,
            "2026-08-30T10:00:00Z",
            outputPath,
            failedRunId: 25);
 
        Assert.Equal(0, result.ExitCode);
        using var output = JsonDocument.Parse(await File.ReadAllTextAsync(outputPath));
        Assert.Equal(24, output.RootElement.GetProperty("id").GetInt64());
        Assert.Equal("earlier", output.RootElement.GetProperty("head_sha").GetString());
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task LastSuccessfulMainRunKeepsPushFilterAcrossPages()
    {
        var fakeGh = """
            #!/usr/bin/env bash
            echo "$*" >> "${GH_CALL_LOG}"
            if [[ "$*" == *"page=2"* ]]; then
              echo '{"total_count":101,"workflow_runs":[{"id":101,"created_at":"2026-08-30T09:30:00Z","head_sha":"page-two"}]}'
            else
              jq -n '{
                total_count: 101,
                workflow_runs: [range(100; 0; -1) | {
                  id: .,
                  created_at: "2026-08-30T09:00:00Z",
                  head_sha: "page-one"
                }]
              }'
            fi
            """;
 
        var outputPath = Path.Combine(_workspace.Path, "last-success.json");
        var result = await RunHistoryScriptAsync(fakeGh, "2026-08-30T10:00:00Z", outputPath);
 
        Assert.Equal(0, result.ExitCode);
        using var output = JsonDocument.Parse(await File.ReadAllTextAsync(outputPath));
        Assert.Equal(101, output.RootElement.GetProperty("id").GetInt64());
        Assert.All(
            await File.ReadAllLinesAsync(Path.Combine(_workspace.Path, "gh-calls.log")),
            call =>
            {
                Assert.Contains("branch=main", call, StringComparison.Ordinal);
                Assert.Contains("event=push", call, StringComparison.Ordinal);
                Assert.Contains("status=success", call, StringComparison.Ordinal);
            });
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task LastSuccessfulMainRunContinuesPastStaleTotalCount()
    {
        var fakeGh = """
            #!/usr/bin/env bash
            echo "$*" >> "${GH_CALL_LOG}"
            if [[ "$*" == *"page=3"* ]]; then
              echo '{"total_count":150,"workflow_runs":[{"id":301,"created_at":"2026-08-30T09:45:00Z","head_sha":"page-three"}]}'
            elif [[ "$*" == *"page=2"* ]]; then
              jq -n '{total_count: 150, workflow_runs: [range(201; 101; -1) | {
                id: ., created_at: "2026-08-30T09:30:00Z", head_sha: "page-two"
              }]}'
            else
              jq -n '{total_count: 150, workflow_runs: [range(101; 1; -1) | {
                id: ., created_at: "2026-08-30T09:00:00Z", head_sha: "page-one"
              }]}'
            fi
            """;
        var outputPath = Path.Combine(_workspace.Path, "last-success.json");
 
        var result = await RunHistoryScriptAsync(fakeGh, "2026-08-30T10:00:00Z", outputPath);
 
        Assert.Equal(0, result.ExitCode);
        using var output = JsonDocument.Parse(await File.ReadAllTextAsync(outputPath));
        Assert.Equal(301, output.RootElement.GetProperty("id").GetInt64());
        Assert.Contains(
            await File.ReadAllLinesAsync(Path.Combine(_workspace.Path, "gh-calls.log")),
            call => call.Contains("page=3", StringComparison.Ordinal));
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task LastSuccessfulMainRunRejectsPartialPagination()
    {
        var fakeGh = """
            #!/usr/bin/env bash
            if [[ "$*" == *"page=2"* ]]; then
              echo '{"total_count":150,"workflow_runs":[{"id":101,"created_at":"2026-08-30T09:30:00Z","head_sha":"partial"}]}'
            else
              jq -n '{total_count: 150, workflow_runs: [range(100; 0; -1) | {
                id: ., created_at: "2026-08-30T09:00:00Z", head_sha: "page-one"
              }]}'
            fi
            """;
 
        var result = await RunHistoryScriptAsync(
            fakeGh,
            "2026-08-30T10:00:00Z",
            Path.Combine(_workspace.Path, "last-success.json"));
 
        Assert.NotEqual(0, result.ExitCode);
        Assert.Contains("GitHub returned only 101 of 150 unique workflow runs.", result.Output, StringComparison.Ordinal);
    }
 
    [Theory]
    [InlineData("""{"workflow_runs":[{"id":20,"created_at":"2026-08-30T09:00:00Z","head_sha":"latest"}]}""")]
    [InlineData("""{"total_count":"1","workflow_runs":[{"id":20,"created_at":"2026-08-30T09:00:00Z","head_sha":"latest"}]}""")]
    [InlineData("""{"total_count":1,"workflow_runs":[{"id":20,"created_at":"2026-08-30T09:00:00Z"}]}""")]
    [RequiresTools(["bash", "jq"])]
    public async Task LastSuccessfulMainRunRejectsMalformedMetadata(string response)
    {
        var fakeGh = $"#!/usr/bin/env bash\nprintf '%s\\n' '{response}'";
 
        var result = await RunHistoryScriptAsync(
            fakeGh,
            "2026-08-30T10:00:00Z",
            Path.Combine(_workspace.Path, "last-success.json"));
 
        Assert.NotEqual(0, result.ExitCode);
        Assert.Contains("GitHub returned invalid workflow-run metadata.", result.Output, StringComparison.Ordinal);
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task LastSuccessfulMainRunSubdividesCappedWindows()
    {
        var fakeGh = """
            #!/usr/bin/env bash
            echo "$*" >> "${GH_CALL_LOG}"
            call_count_file="${GH_CALL_COUNT_FILE}"
            call_count=$(cat "$call_count_file" 2>/dev/null || echo 0)
            call_count=$((call_count + 1))
            echo "$call_count" > "$call_count_file"
            if [ "$call_count" -eq 1 ]; then
              echo '{"total_count":1000,"workflow_runs":[]}'
            else
              echo '{"total_count":1,"workflow_runs":[{"id":77,"created_at":"2026-08-30T09:30:00Z","head_sha":"subdivided"}]}'
            fi
            """;
 
        var result = await RunHistoryScriptAsync(
            fakeGh,
            "2026-08-30T10:00:00Z",
            Path.Combine(_workspace.Path, "last-success.json"));
 
        Assert.Equal(0, result.ExitCode);
        using var output = JsonDocument.Parse(await File.ReadAllTextAsync(Path.Combine(_workspace.Path, "last-success.json")));
        Assert.Equal(77, output.RootElement.GetProperty("id").GetInt64());
        Assert.True(int.Parse(await File.ReadAllTextAsync(Path.Combine(_workspace.Path, "gh-call-count"))) >= 2);
        Assert.True(
            (await File.ReadAllLinesAsync(Path.Combine(_workspace.Path, "gh-calls.log")))
                .Select(line => line[(line.IndexOf("created=", StringComparison.Ordinal))..])
                .Distinct(StringComparer.Ordinal)
                .Count() >= 2);
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task LastSuccessfulMainRunFallsBackToOlderWindow()
    {
        var fakeGh = """
            #!/usr/bin/env bash
            echo "$*" >> "${GH_CALL_LOG}"
            call_count_file="${GH_CALL_COUNT_FILE}"
            call_count=$(cat "$call_count_file" 2>/dev/null || echo 0)
            call_count=$((call_count + 1))
            echo "$call_count" > "$call_count_file"
            if [ "$call_count" -eq 1 ]; then
              echo '{"total_count":0,"workflow_runs":[]}'
            else
              echo '{"total_count":1,"workflow_runs":[{"id":55,"created_at":"2026-08-28T09:00:00Z","head_sha":"older-window"}]}'
            fi
            """;
 
        var result = await RunHistoryScriptAsync(
            fakeGh,
            "2026-08-30T10:00:00Z",
            Path.Combine(_workspace.Path, "last-success.json"));
 
        Assert.Equal(0, result.ExitCode);
        using var output = JsonDocument.Parse(await File.ReadAllTextAsync(Path.Combine(_workspace.Path, "last-success.json")));
        Assert.Equal(55, output.RootElement.GetProperty("id").GetInt64());
        Assert.Equal(2, int.Parse(await File.ReadAllTextAsync(Path.Combine(_workspace.Path, "gh-call-count"))));
        Assert.Equal(
            2,
            (await File.ReadAllLinesAsync(Path.Combine(_workspace.Path, "gh-calls.log")))
                .Select(line => line[(line.IndexOf("created=", StringComparison.Ordinal))..])
                .Distinct(StringComparer.Ordinal)
                .Count());
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task LastSuccessfulMainRunSurfacesApiFailure()
    {
        var result = await RunHistoryScriptAsync(
            "#!/usr/bin/env bash\nexit 1",
            "2026-08-30T10:00:00Z",
            Path.Combine(_workspace.Path, "last-success.json"));
 
        Assert.NotEqual(0, result.ExitCode);
    }
 
    [Theory]
    [InlineData("""[{"commits":[]}]""")]
    [InlineData("""[{"total_commits":"0","commits":[]}]""")]
    [InlineData("""[{"total_commits":1,"commits":[{"sha":"broken"}]}]""")]
    [RequiresTools(["bash", "jq"])]
    public async Task CandidateMergeCollectionRejectsMalformedComparisonMetadata(string response)
    {
        var fakeGh = $"#!/usr/bin/env bash\nprintf '%s\\n' '{response}'";
        var candidatesPath = Path.Combine(_workspace.Path, "candidate-merges.json");
        var statusPath = Path.Combine(_workspace.Path, "candidate-merge-history-status.json");
 
        var result = await RunCandidateScriptAsync(fakeGh, candidatesPath, statusPath);
 
        Assert.Equal(0, result.ExitCode);
        Assert.Equal("[]" + Environment.NewLine, await File.ReadAllTextAsync(candidatesPath));
        using var status = JsonDocument.Parse(await File.ReadAllTextAsync(statusPath));
        Assert.Equal("unavailable", status.RootElement.GetProperty("state").GetString());
    }
 
    [Theory]
    [InlineData("""[{"status":"identical","total_commits":0,"commits":[]}]""")]
    [InlineData("""[{"status":"behind","total_commits":0,"commits":[]}]""")]
    [InlineData("""[{"status":"diverged","total_commits":1,"commits":[{"sha":"diverged","commit":{"message":"Diverged commit"},"html_url":"https://github.com/microsoft/aspire/commit/diverged"}]}]""")]
    [InlineData("""[{"status":"unknown","total_commits":0,"commits":[]}]""")]
    [InlineData("""[{"status":"ahead","total_commits":0,"commits":[]}]""")]
    [RequiresTools(["bash", "jq"])]
    public async Task CandidateMergeCollectionRequiresAheadComparison(string response)
    {
        var fakeGh = $"#!/usr/bin/env bash\nprintf '%s\\n' '{response}'";
        var candidatesPath = Path.Combine(_workspace.Path, "candidate-merges.json");
        var statusPath = Path.Combine(_workspace.Path, "candidate-merge-history-status.json");
 
        var result = await RunCandidateScriptAsync(fakeGh, candidatesPath, statusPath);
 
        Assert.Equal(0, result.ExitCode);
        Assert.Equal("[]" + Environment.NewLine, await File.ReadAllTextAsync(candidatesPath));
        using var status = JsonDocument.Parse(await File.ReadAllTextAsync(statusPath));
        Assert.Equal("unavailable", status.RootElement.GetProperty("state").GetString());
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task CandidateMergeCollectionRequiresEveryUniqueCommit()
    {
        var fakeGh = """
            #!/usr/bin/env bash
            case "$*" in
              *"compare/trusted-success...trusted-failure"*)
                cat <<'JSON'
            [
              {
                "status": "ahead",
                "total_commits": 2,
                "commits": [
                  {"sha":"duplicate","commit":{"message":"Duplicate commit"},"html_url":"https://github.com/microsoft/aspire/commit/duplicate"}
                ]
              },
              {
                "commits": [
                  {"sha":"duplicate","commit":{"message":"Duplicate commit"},"html_url":"https://github.com/microsoft/aspire/commit/duplicate"}
                ]
              }
            ]
            JSON
                ;;
              *"commits/duplicate/pulls"*)
                echo '[[{"number":41,"title":"Associated PR","html_url":"https://github.com/microsoft/aspire/pull/41","merged_at":"2026-08-30T00:00:00Z","base":{"repo":{"full_name":"microsoft/aspire"},"ref":"main"}}]]'
                ;;
              *)
                exit 99
                ;;
            esac
            """;
        var candidatesPath = Path.Combine(_workspace.Path, "candidate-merges.json");
        var statusPath = Path.Combine(_workspace.Path, "candidate-merge-history-status.json");
 
        var result = await RunCandidateScriptAsync(fakeGh, candidatesPath, statusPath);
 
        Assert.Equal(0, result.ExitCode);
        using var candidates = JsonDocument.Parse(await File.ReadAllTextAsync(candidatesPath));
        Assert.Single(candidates.RootElement.EnumerateArray());
        using var status = JsonDocument.Parse(await File.ReadAllTextAsync(statusPath));
        Assert.Equal("incomplete", status.RootElement.GetProperty("state").GetString());
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task CandidateMergeCollectionPreservesResultsWhenAssociationIsIncomplete()
    {
        var fakeGh = """
            #!/usr/bin/env bash
            echo "$*" >> "${GH_CALL_LOG}"
            case "$*" in
              *"compare/trusted-success...trusted-failure"*)
                cat <<'JSON'
            [
              {
                "status": "ahead",
                "total_commits": 2,
                "commits": [
                  {"sha":"unavailable","commit":{"message":"Unavailable commit"},"html_url":"https://github.com/microsoft/aspire/commit/unavailable"}
                ]
              },
              {
                "commits": [
                  {"sha":"associated","commit":{"message":"Associated commit"},"html_url":"https://github.com/microsoft/aspire/commit/associated"}
                ]
              }
            ]
            JSON
                ;;
              *"commits/associated/pulls"*)
                echo '[[{"number":41,"title":"Associated PR","html_url":"https://github.com/microsoft/aspire/pull/41","merged_at":"2026-08-30T00:00:00Z","base":{"repo":{"full_name":"microsoft/aspire"},"ref":"main"}}]]'
                ;;
              *"commits/unavailable/pulls"*)
                exit 1
                ;;
              *)
                exit 99
                ;;
            esac
            """;
        var candidatesPath = Path.Combine(_workspace.Path, "candidate-merges.json");
        var statusPath = Path.Combine(_workspace.Path, "candidate-merge-history-status.json");
 
        var result = await RunCandidateScriptAsync(fakeGh, candidatesPath, statusPath);
 
        Assert.Equal(0, result.ExitCode);
        using var candidates = JsonDocument.Parse(await File.ReadAllTextAsync(candidatesPath));
        var candidate = Assert.Single(candidates.RootElement.EnumerateArray());
        Assert.Equal("associated", candidate.GetProperty("sha").GetString());
        Assert.Equal(41, candidate.GetProperty("pull_request").GetProperty("number").GetInt32());
        using var status = JsonDocument.Parse(await File.ReadAllTextAsync(statusPath));
        Assert.Equal("incomplete", status.RootElement.GetProperty("state").GetString());
        var ghCalls = await File.ReadAllLinesAsync(Path.Combine(_workspace.Path, "gh-calls.log"));
        Assert.Contains(
            ghCalls,
            call => call.Contains("compare/trusted-success...trusted-failure", StringComparison.Ordinal)
                && call.Contains("--paginate", StringComparison.Ordinal)
                && call.Contains("--slurp", StringComparison.Ordinal));
        Assert.Contains(ghCalls, call => call.Contains("commits/unavailable/pulls", StringComparison.Ordinal));
        Assert.Contains(ghCalls, call => call.Contains("commits/associated/pulls", StringComparison.Ordinal));
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task CandidateMergeCollectionFindsAssociationOnLaterPage()
    {
        var fakeGh = """
            #!/usr/bin/env bash
            echo "$*" >> "${GH_CALL_LOG}"
            case "$*" in
              *"compare/trusted-success...trusted-failure"*)
                cat <<'JSON'
            [
              {
                "status": "ahead",
                "total_commits": 1,
                "commits": [
                  {"sha":"associated","commit":{"message":"Associated commit"},"html_url":"https://github.com/microsoft/aspire/commit/associated"}
                ]
              }
            ]
            JSON
                ;;
              *"commits/associated/pulls"*)
                if [[ "$*" == *"--paginate"* && "$*" == *"--slurp"* && "$*" == *"per_page=100"* ]]; then
                  cat <<'JSON'
            [
              [{"number":17,"merged_at":null,"base":{"repo":{"full_name":"microsoft/aspire"},"ref":"main"}}],
              [{"number":41,"title":"Associated PR","html_url":"https://github.com/microsoft/aspire/pull/41","merged_at":"2026-08-30T00:00:00Z","base":{"repo":{"full_name":"microsoft/aspire"},"ref":"main"}}]
            ]
            JSON
                else
                  echo 'null'
                fi
                ;;
              *)
                exit 99
                ;;
            esac
            """;
        var candidatesPath = Path.Combine(_workspace.Path, "candidate-merges.json");
        var statusPath = Path.Combine(_workspace.Path, "candidate-merge-history-status.json");
 
        var result = await RunCandidateScriptAsync(fakeGh, candidatesPath, statusPath);
 
        Assert.Equal(0, result.ExitCode);
        using var candidates = JsonDocument.Parse(await File.ReadAllTextAsync(candidatesPath));
        var candidate = Assert.Single(candidates.RootElement.EnumerateArray());
        Assert.Equal(41, candidate.GetProperty("pull_request").GetProperty("number").GetInt32());
        using var status = JsonDocument.Parse(await File.ReadAllTextAsync(statusPath));
        Assert.Equal("available", status.RootElement.GetProperty("state").GetString());
        Assert.Contains(
            await File.ReadAllLinesAsync(Path.Combine(_workspace.Path, "gh-calls.log")),
            call => call.Contains("commits/associated/pulls?per_page=100", StringComparison.Ordinal)
                && call.Contains("--paginate", StringComparison.Ordinal)
                && call.Contains("--slurp", StringComparison.Ordinal));
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task CandidateMergeCollectionReportsIncompleteWhenAssociationIsAmbiguous()
    {
        var fakeGh = """
            #!/usr/bin/env bash
            case "$*" in
              *"compare/trusted-success...trusted-failure"*)
                echo '[{"status":"ahead","total_commits":1,"commits":[{"sha":"ambiguous","commit":{"message":"Ambiguous commit"},"html_url":"https://github.com/microsoft/aspire/commit/ambiguous"}]}]'
                ;;
              *"commits/ambiguous/pulls"*)
                cat <<'JSON'
            [[
              {"number":41,"title":"First PR","html_url":"https://github.com/microsoft/aspire/pull/41","merged_at":"2026-08-30T00:00:00Z","base":{"repo":{"full_name":"microsoft/aspire"},"ref":"main"}},
              {"number":42,"title":"Second PR","html_url":"https://github.com/microsoft/aspire/pull/42","merged_at":"2026-08-30T00:01:00Z","base":{"repo":{"full_name":"microsoft/aspire"},"ref":"main"}}
            ]]
            JSON
                ;;
              *)
                exit 99
                ;;
            esac
            """;
        var candidatesPath = Path.Combine(_workspace.Path, "candidate-merges.json");
        var statusPath = Path.Combine(_workspace.Path, "candidate-merge-history-status.json");
 
        var result = await RunCandidateScriptAsync(fakeGh, candidatesPath, statusPath);
 
        Assert.Equal(0, result.ExitCode);
        using var candidates = JsonDocument.Parse(await File.ReadAllTextAsync(candidatesPath));
        Assert.Empty(candidates.RootElement.EnumerateArray());
        using var status = JsonDocument.Parse(await File.ReadAllTextAsync(statusPath));
        Assert.Equal("incomplete", status.RootElement.GetProperty("state").GetString());
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task CandidateMergeCollectionReportsIncompleteWhenAssociationIsMissing()
    {
        var fakeGh = """
            #!/usr/bin/env bash
            case "$*" in
              *"compare/trusted-success...trusted-failure"*)
                cat <<'JSON'
            [
              {
                "status": "ahead",
                "total_commits": 1,
                "commits": [
                  {"sha":"direct","commit":{"message":"Direct commit"},"html_url":"https://github.com/microsoft/aspire/commit/direct"}
                ]
              }
            ]
            JSON
                ;;
              *"commits/direct/pulls"*)
                if [[ "$*" == *"--paginate"* && "$*" == *"--slurp"* ]]; then
                  echo '[[]]'
                else
                  echo 'null'
                fi
                ;;
              *)
                exit 99
                ;;
            esac
            """;
        var candidatesPath = Path.Combine(_workspace.Path, "candidate-merges.json");
        var statusPath = Path.Combine(_workspace.Path, "candidate-merge-history-status.json");
 
        var result = await RunCandidateScriptAsync(fakeGh, candidatesPath, statusPath);
 
        Assert.Equal(0, result.ExitCode);
        using var candidates = JsonDocument.Parse(await File.ReadAllTextAsync(candidatesPath));
        Assert.Empty(candidates.RootElement.EnumerateArray());
        using var status = JsonDocument.Parse(await File.ReadAllTextAsync(statusPath));
        Assert.Equal("incomplete", status.RootElement.GetProperty("state").GetString());
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task CandidateMergeCollectionReportsIncompleteWhenCompareRangeIsTruncated()
    {
        var fakeGh = """
            #!/usr/bin/env bash
            case "$*" in
              *"compare/trusted-success...trusted-failure"*)
                cat <<'JSON'
            [
              {
                "status": "ahead",
                "total_commits": 5,
                "commits": [
                  {"sha":"associated","commit":{"message":"Associated commit"},"html_url":"https://github.com/microsoft/aspire/commit/associated"}
                ]
              }
            ]
            JSON
                ;;
              *"commits/associated/pulls"*)
                echo '[[{"number":41,"title":"Associated PR","html_url":"https://github.com/microsoft/aspire/pull/41","merged_at":"2026-08-30T00:00:00Z","base":{"repo":{"full_name":"microsoft/aspire"},"ref":"main"}}]]'
                ;;
              *)
                exit 99
                ;;
            esac
            """;
        var candidatesPath = Path.Combine(_workspace.Path, "candidate-merges.json");
        var statusPath = Path.Combine(_workspace.Path, "candidate-merge-history-status.json");
 
        var result = await RunCandidateScriptAsync(fakeGh, candidatesPath, statusPath);
 
        Assert.Equal(0, result.ExitCode);
        using var candidates = JsonDocument.Parse(await File.ReadAllTextAsync(candidatesPath));
        var candidate = Assert.Single(candidates.RootElement.EnumerateArray());
        Assert.Equal("associated", candidate.GetProperty("sha").GetString());
        using var status = JsonDocument.Parse(await File.ReadAllTextAsync(statusPath));
        Assert.Equal("incomplete", status.RootElement.GetProperty("state").GetString());
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task PersistedMainAnalysisRebuildsAllContextFromTrustedArtifacts()
    {
        await WritePersistenceFixtureAsync(
            """
            {
              "run_id": 999,
              "run_attempt": 99,
              "run_url": "https://evil.example/run",
              "run_scope": "main",
              "analyzed_at": "1999-01-01T00:00:00Z",
              "verdict": "main-repository-breakage",
              "pr": null,
              "triggering_merge_pr": {"number":999,"title":"forged triggering merge"},
              "main_context": {"last_successful_main_sha":"forged","failed_sha":"forged","candidate_merges":[{"sha":"forged"}]},
              "failed_jobs": [{"id":123,"classification":"main-repository-breakage","reason":"compiler failed"}],
              "failed_tests": [],
              "causes": ["main-build-break"]
            }
            """,
            """{"run_id":123,"run_attempt":2,"run_scope":"main","head_sha":"trusted-failed","pr_numbers":""}""",
            """{"html_url":"https://github.com/microsoft/aspire/actions/runs/123"}""",
            """[{"id":123,"name":"Build","conclusion":"failure","html_url":"https://github.com/job/123","steps":[{"name":"ConnectionStrings__step=step-secret","conclusion":"failure"}]}]""",
            """{"number":42,"title":"PrimaryKey=trigger-secret","html_url":"https://github.com/microsoft/aspire/pull/42"}""",
            """{"head_sha":"trusted-success"}""",
            """[{"sha":"trusted-candidate","message":"SecondaryKey=candidate-secret\nsecond line","html_url":"https://github.com/commit","pull_request":{"number":41,"title":"ConnectionString=candidate-pr-secret","url":"https://github.com/microsoft/aspire/pull/41","merged_at":"2026-08-29T00:00:00Z"}}]""",
            "{}",
            """{"state":"available"}""");
 
        var outputPath = Path.Combine(_workspace.Path, "persisted-main.json");
        var result = await RunPersistenceScriptAsync("write-run-summary", outputPath);
 
        Assert.Equal(0, result.ExitCode);
        using var document = JsonDocument.Parse(await File.ReadAllTextAsync(outputPath));
        var root = document.RootElement;
        Assert.Equal(12, root.EnumerateObject().Count());
        Assert.Equal(123, root.GetProperty("run_id").GetInt64());
        Assert.Equal(2, root.GetProperty("run_attempt").GetInt32());
        Assert.Equal("https://github.com/microsoft/aspire/actions/runs/123", root.GetProperty("run_url").GetString());
        Assert.Equal("main", root.GetProperty("run_scope").GetString());
        Assert.Equal("2026-08-31T12:00:00Z", root.GetProperty("analyzed_at").GetString());
        Assert.Equal(JsonValueKind.Null, root.GetProperty("pr").ValueKind);
 
        var triggeringMerge = root.GetProperty("triggering_merge_pr");
        Assert.Equal(8, triggeringMerge.EnumerateObject().Count());
        Assert.Equal(42, triggeringMerge.GetProperty("number").GetInt32());
        Assert.Equal("PrimaryKey=[REDACTED]", triggeringMerge.GetProperty("title").GetString());
        Assert.Equal("https://github.com/microsoft/aspire/pull/42", triggeringMerge.GetProperty("url").GetString());
 
        var mainContext = root.GetProperty("main_context");
        Assert.Equal(4, mainContext.EnumerateObject().Count());
        Assert.Equal("trusted-failed", mainContext.GetProperty("failed_sha").GetString());
        Assert.Equal("trusted-success", mainContext.GetProperty("last_successful_main_sha").GetString());
        Assert.Equal("available", mainContext.GetProperty("candidate_merge_history_state").GetString());
        Assert.Equal("trusted-candidate", mainContext.GetProperty("candidate_merges")[0].GetProperty("sha").GetString());
        Assert.Equal(
            "SecondaryKey=[REDACTED] second line",
            mainContext.GetProperty("candidate_merges")[0].GetProperty("message").GetString());
        Assert.Equal(
            "ConnectionString=[REDACTED]",
            mainContext.GetProperty("candidate_merges")[0].GetProperty("pull_request").GetProperty("title").GetString());
 
        var failedJob = root.GetProperty("failed_jobs")[0];
        Assert.Equal(7, failedJob.EnumerateObject().Count());
        Assert.Equal("Build", failedJob.GetProperty("name").GetString());
        Assert.Equal("main-repository-breakage", failedJob.GetProperty("classification").GetString());
        Assert.Equal("compiler failed", failedJob.GetProperty("reason").GetString());
        Assert.Equal("ConnectionStrings__step=[REDACTED]", failedJob.GetProperty("failed_steps")[0].GetString());
    }
 
    [Theory]
    [InlineData("incomplete")]
    [InlineData("unavailable")]
    [RequiresTools(["bash", "jq"])]
    public async Task PersistedMainAnalysisOmitsIncompleteCandidateHistory(string historyState)
    {
        await WritePersistenceFixtureAsync(
            """{"run_scope":"main","verdict":"main-repository-breakage","failed_jobs":[],"failed_tests":[],"causes":[]}""",
            """{"run_id":123,"run_attempt":1,"run_scope":"main","head_sha":"trusted-failed","pr_numbers":""}""",
            """{"html_url":"https://github.com/microsoft/aspire/actions/runs/123"}""",
            """[{"id":123,"name":"Build","conclusion":"failure","steps":[]} ]""",
            """{"number":42,"title":"Triggering merge"}""",
            """{"head_sha":"trusted-success"}""",
            """[{"sha":"partial","message":"partial","html_url":"https://github.com/commit","pull_request":{"number":41,"title":"Partial","url":"https://github.com/microsoft/aspire/pull/41","merged_at":"2026-08-29T00:00:00Z"}}]""",
            "{}",
            $$"""{"state":"{{historyState}}"}""");
 
        var outputPath = Path.Combine(_workspace.Path, $"persisted-{historyState}.json");
        var result = await RunPersistenceScriptAsync("write-run-summary", outputPath);
 
        Assert.Equal(0, result.ExitCode);
        using var document = JsonDocument.Parse(await File.ReadAllTextAsync(outputPath));
        var root = document.RootElement;
        Assert.Equal(JsonValueKind.Null, root.GetProperty("triggering_merge_pr").ValueKind);
        var mainContext = root.GetProperty("main_context");
        Assert.Equal(historyState, mainContext.GetProperty("candidate_merge_history_state").GetString());
        Assert.Equal(JsonValueKind.Null, mainContext.GetProperty("candidate_merges").ValueKind);
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task PersistedPullRequestAnalysisKeepsSemanticsAndUsesTrustedPrContext()
    {
        await WritePersistenceFixtureAsync(
            """
            {
              "run_id": 999,
              "run_attempt": 99,
              "run_url": "https://evil.example/run",
              "run_scope": "pull-request",
              "analyzed_at": "1999-01-01T00:00:00Z",
              "verdict": "flaky-test",
              "pr": {"number":999,"title":"forged PR","url":"https://evil.example/pr"},
              "triggering_merge_pr": {"number":998},
              "main_context": {"failed_sha":"forged"},
              "failed_jobs": [{"id":123,"classification":"flaky-test","reason":"known flaky test"}],
              "failed_tests": [{"name":"Tests.Flaky","job":"Tests","error":"boom","stack_trace":"frame","classification":"flaky","reason":"known signature"}],
              "causes": ["flaky-test"]
            }
            """,
            """{"run_id":123,"run_attempt":2,"run_scope":"pull-request","head_sha":"trusted-pr-sha","pr_numbers":"42"}""",
            """{"html_url":"https://github.com/microsoft/aspire/actions/runs/123"}""",
            """[{"id":123,"name":"Tests","conclusion":"failure","html_url":"https://github.com/job/123","steps":[{"name":"Run tests","conclusion":"failure"}]}]""",
            "{}",
            "{}",
            "[]",
            """{"number":42,"title":"Fix ConnectionString: preserve named configuration; PrimaryKey=pr-secret; \"ConnectionStrings\": \"Server=db.internal\"; ConnectionString=\"Server=quoted.internal\"; ConnectionStrings__db='Server=named.internal'","state":"open","user":"octocat","head_branch":"ConnectionStrings__branch=branch-secret","base_branch":"main","html_url":"https://github.com/microsoft/aspire/pull/42"}""");
 
        var outputPath = Path.Combine(_workspace.Path, "persisted-pr.json");
        var result = await RunPersistenceScriptAsync("write-run-summary", outputPath);
 
        Assert.Equal(0, result.ExitCode);
        using var document = JsonDocument.Parse(await File.ReadAllTextAsync(outputPath));
        var root = document.RootElement;
        Assert.Equal(12, root.EnumerateObject().Count());
        Assert.Equal(123, root.GetProperty("run_id").GetInt64());
        Assert.Equal(2, root.GetProperty("run_attempt").GetInt32());
        Assert.Equal("https://github.com/microsoft/aspire/actions/runs/123", root.GetProperty("run_url").GetString());
        Assert.Equal("2026-08-31T12:00:00Z", root.GetProperty("analyzed_at").GetString());
 
        var pr = root.GetProperty("pr");
        Assert.Equal(7, pr.EnumerateObject().Count());
        Assert.Equal(42, pr.GetProperty("number").GetInt32());
        Assert.Equal(
            "Fix ConnectionString: preserve named configuration; PrimaryKey=[REDACTED]; \"ConnectionStrings\": \"[REDACTED]\"; ConnectionString=\"[REDACTED]\"; ConnectionStrings__db='[REDACTED]'",
            pr.GetProperty("title").GetString());
        Assert.Equal("ConnectionStrings__branch=[REDACTED]", pr.GetProperty("head_branch").GetString());
        Assert.Equal("https://github.com/microsoft/aspire/pull/42", pr.GetProperty("url").GetString());
        Assert.Equal("known flaky test", root.GetProperty("failed_jobs")[0].GetProperty("reason").GetString());
        Assert.Equal("Tests.Flaky", root.GetProperty("failed_tests")[0].GetProperty("name").GetString());
        Assert.Equal(JsonValueKind.Null, root.GetProperty("triggering_merge_pr").ValueKind);
        Assert.Equal(JsonValueKind.Null, root.GetProperty("main_context").ValueKind);
    }
 
    [Theory]
    [InlineData("Tests", "Tests")]
    [InlineData("Forged job", "")]
    [InlineData("Tests extra", "")]
    [RequiresTools(["bash", "jq"])]
    public async Task PersistedFailedTestKeepsOnlyExactTrustedJobName(string reportedJob, string expectedJob)
    {
        await WritePersistenceFixtureAsync(
            $$"""
            {
              "run_id": 123,
              "run_scope": "pull-request",
              "verdict": "flaky-test",
              "pr": {"number":42},
              "failed_jobs": [{"id":123,"classification":"flaky-test","reason":"known flaky test"}],
              "failed_tests": [{"name":"Tests.Flaky","job":"{{reportedJob}}","error":"boom","stack_trace":"","classification":"flaky","reason":"known signature"}],
              "causes": ["flaky-test"]
            }
            """,
            """{"run_id":123,"run_attempt":1,"run_scope":"pull-request","head_sha":"trusted-pr-sha","pr_numbers":"42"}""",
            """{"html_url":"https://github.com/microsoft/aspire/actions/runs/123"}""",
            """[{"id":123,"name":"Tests","conclusion":"failure","html_url":"https://github.com/job/123","steps":[]}]""",
            "{}",
            "{}",
            "[]",
            """{"number":42}""");
 
        var outputPath = Path.Combine(_workspace.Path, "persisted-pr.json");
        var result = await RunPersistenceScriptAsync("write-run-summary", outputPath);
 
        Assert.Equal(0, result.ExitCode);
        using var document = JsonDocument.Parse(await File.ReadAllTextAsync(outputPath));
        Assert.Equal(expectedJob, document.RootElement.GetProperty("failed_tests")[0].GetProperty("job").GetString());
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task PersistedAnalysisNormalizesTrustedJobNamesBeforeMatching()
    {
        await WritePersistenceFixtureAsync(
            """
            {
              "run_id": 123,
              "run_scope": "pull-request",
              "verdict": "flaky-test",
              "pr": {"number":42},
              "failed_jobs": [{"id":123,"classification":"flaky-test","reason":"known flaky test"}],
              "failed_tests": [{"name":"Tests.Flaky","job":"Tests Linux","error":"boom","stack_trace":"","classification":"flaky","reason":"known signature"}],
              "causes": ["flaky-test"]
            }
            """,
            """{"run_id":123,"run_attempt":1,"run_scope":"pull-request","head_sha":"trusted-pr-sha","pr_numbers":"42"}""",
            """{"html_url":"https://github.com/microsoft/aspire/actions/runs/123"}""",
            """[{"id":123,"name":"Tests\r\nLinux","conclusion":"failure","html_url":"https://github.com/job/123","steps":[]}]""",
            "{}",
            "{}",
            "[]",
            """{"number":42}""");
 
        var outputPath = Path.Combine(_workspace.Path, "persisted-pr.json");
        var result = await RunPersistenceScriptAsync("write-run-summary", outputPath);
 
        Assert.Equal(0, result.ExitCode);
        using var document = JsonDocument.Parse(await File.ReadAllTextAsync(outputPath));
        Assert.Equal("Tests Linux", document.RootElement.GetProperty("failed_jobs")[0].GetProperty("name").GetString());
        Assert.Equal("Tests Linux", document.RootElement.GetProperty("failed_tests")[0].GetProperty("job").GetString());
    }
 
    [Theory]
    [InlineData("main", "", "0")]
    [InlineData("pull-request", "42", "42")]
    [InlineData("pull-request", "42,43", "0")]
    [RequiresTools(["bash", "jq"])]
    public async Task PersistedOccurrenceUsesOnlyTrustedSubjectPr(
        string runScope,
        string trustedPrNumbers,
        string expectedPrNumber)
    {
        var failureDataDirectory = Directory.CreateDirectory(Path.Combine(_workspace.Path, "ci-failure-data")).FullName;
        await File.WriteAllTextAsync(
            Path.Combine(failureDataDirectory, "run-context.json"),
            $$"""{"run_id":123,"run_scope":"{{runScope}}","pr_numbers":"{{trustedPrNumbers}}"}""");
        var causeFile = Path.Combine(_workspace.Path, "cause.json");
        await File.WriteAllTextAsync(
            causeFile,
            """{"id":"test-failure","type":"flaky-test","title":"Test failure","error_pattern":"boom"}""");
 
        var result = await RunBashScriptAsync(
            Path.Combine(RepoRoot.Path, PersistenceScriptRelativePath),
            ["add-occurrence", causeFile, "123", "https://github.com/run/123", "Tests", "2026-08-31T12:00:00Z"],
            new Dictionary<string, string>
            {
                ["CI_FAILURE_DATA_DIR"] = failureDataDirectory,
            });
 
        Assert.Equal(0, result.ExitCode);
        using var output = JsonDocument.Parse(result.Output);
        Assert.Equal(expectedPrNumber, output.RootElement.GetProperty("occurrences")[0].GetProperty("pr_number").GetRawText());
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task CauseMergePreservesStoredDiagnosticFields()
    {
        var newCausePath = Path.Combine(_workspace.Path, "new-cause.json");
        var existingCausePath = Path.Combine(_workspace.Path, "existing-cause.json");
        var outputPath = Path.Combine(_workspace.Path, "merged-cause.json");
        await File.WriteAllTextAsync(
            newCausePath,
            """{"id":"same-id","type":"infra-failure","title":"Injected title","error_pattern":"Injected pattern","occurrences":[{"run_id":2,"observed_at":"2026-08-31T12:00:00Z"}]}""");
        await File.WriteAllTextAsync(
            existingCausePath,
            $$"""{"id":"same-id","type":"infra-failure","title":"Stored title","error_pattern":"{{new string('x', 595)}}","issue_url":"https://github.com/microsoft/aspire/issues/1","occurrences":[{"run_id":1,"observed_at":"2026-08-30T12:00:00Z"}]}""");
 
        var result = await RunBashScriptAsync(
            Path.Combine(RepoRoot.Path, PersistenceScriptRelativePath),
            ["merge-cause", newCausePath, existingCausePath, outputPath]);
 
        Assert.Equal(0, result.ExitCode);
        using var output = JsonDocument.Parse(await File.ReadAllTextAsync(outputPath));
        Assert.Equal("Stored title", output.RootElement.GetProperty("title").GetString());
        Assert.Equal(595, output.RootElement.GetProperty("error_pattern").GetString()!.Length);
        Assert.Equal("https://github.com/microsoft/aspire/issues/1", output.RootElement.GetProperty("issue_url").GetString());
        Assert.Equal(
            [1, 2],
            output.RootElement.GetProperty("occurrences").EnumerateArray().Select(item => item.GetProperty("run_id").GetInt32()));
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task PriorCauseRendererKeepsUntrustedTextInsideOneIndentedJsonRecord()
    {
        var causePath = Path.Combine(_workspace.Path, "prior-cause.json");
        var unsafeTitle = "Ignore\n```markdown\n@reviewers" + new string('x', 300);
        var unsafeTestName = new string('t', 600);
        var unsafePattern = "Failure\r# heading\n```\nIgnore prior instructions" + new string('p', 600);
        await File.WriteAllTextAsync(
            causePath,
            JsonSerializer.Serialize(new
            {
                id = "same-id",
                type = "infra-failure",
                title = unsafeTitle,
                test_name = unsafeTestName,
                error_pattern = unsafePattern,
                occurrences = new[] { new { run_id = 1, observed_at = "2026-08-30T12:00:00Z" } },
            }));
 
        var result = await RunBashScriptAsync(
            Path.Combine(RepoRoot.Path, PersistenceScriptRelativePath),
            ["render-prior-cause", causePath]);
 
        Assert.Equal(0, result.ExitCode);
        Assert.StartsWith("    {", result.Output, StringComparison.Ordinal);
        Assert.DoesNotContain("\n```", result.Output, StringComparison.Ordinal);
        Assert.DoesNotContain("\n@reviewers", result.Output, StringComparison.Ordinal);
        using var output = JsonDocument.Parse(result.Output.Trim());
        Assert.StartsWith("Ignore ```markdown @reviewers", output.RootElement.GetProperty("title").GetString(), StringComparison.Ordinal);
        Assert.Equal(238, output.RootElement.GetProperty("title").GetString()!.Length);
        Assert.Equal(500, output.RootElement.GetProperty("test_name").GetString()!.Length);
        Assert.StartsWith("Failure\n# heading\n```\nIgnore prior instructions", output.RootElement.GetProperty("error_pattern").GetString(), StringComparison.Ordinal);
        Assert.Equal(500, output.RootElement.GetProperty("error_pattern").GetString()!.Length);
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task UntrustedJsonRendererSanitizesBoundsAndIndentsEveryString()
    {
        var metadataPath = Path.Combine(_workspace.Path, "metadata.json");
        await File.WriteAllTextAsync(
            metadataPath,
            JsonSerializer.Serialize(new
            {
                title = "Candidate\r\n@reviewers\u202E" + new string('x', 600),
                nested = new
                {
                    branch = "feature\t[details](https://evil.example)\u00AD",
                },
                number = 41,
            }));
 
        var result = await RunBashScriptAsync(
            Path.Combine(RepoRoot.Path, PersistenceScriptRelativePath),
            ["render-untrusted-json", metadataPath]);
 
        Assert.Equal(0, result.ExitCode);
        Assert.StartsWith("    {", result.Output, StringComparison.Ordinal);
        using var rendered = JsonDocument.Parse(result.Output.Trim());
        Assert.Equal(500, rendered.RootElement.GetProperty("title").GetString()!.Length);
        Assert.StartsWith("Candidate @reviewers", rendered.RootElement.GetProperty("title").GetString(), StringComparison.Ordinal);
        Assert.Equal(
            "feature [details](https://evil.example)",
            rendered.RootElement.GetProperty("nested").GetProperty("branch").GetString());
        Assert.Equal(41, rendered.RootElement.GetProperty("number").GetInt32());
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task UntrustedJsonRendererPreservesBoundedMultilineDiagnostics()
    {
        var diagnosticsPath = Path.Combine(_workspace.Path, "diagnostics.json");
        await File.WriteAllTextAsync(
            diagnosticsPath,
            JsonSerializer.Serialize(new
            {
                stack_trace = "first\r\nsecond\u202E\n" + new string('x', 2100),
            }));
 
        var result = await RunBashScriptAsync(
            Path.Combine(RepoRoot.Path, PersistenceScriptRelativePath),
            ["render-untrusted-json", diagnosticsPath, "2000", "multiline"]);
 
        Assert.Equal(0, result.ExitCode);
        using var rendered = JsonDocument.Parse(result.Output.Trim());
        var stackTrace = rendered.RootElement.GetProperty("stack_trace").GetString()!;
        Assert.StartsWith("first\nsecond\n", stackTrace, StringComparison.Ordinal);
        Assert.Equal(2000, stackTrace.Length);
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task UntrustedTextRendererKeepsFenceBreakoutsInsideBoundedIndentedBlock()
    {
        var logPath = Path.Combine(_workspace.Path, "job.log");
        await File.WriteAllTextAsync(
            logPath,
            "first\r\n\u001b[31m```\r\n@reviewers [details](https://evil.example)\u202E\n" + new string('x', 70000));
 
        var result = await RunBashScriptAsync(
            Path.Combine(RepoRoot.Path, PersistenceScriptRelativePath),
            ["render-untrusted-text", logPath, "65536"]);
 
        Assert.Equal(0, result.ExitCode);
        var outputLines = result.Output.ReplaceLineEndings("\n").Split('\n');
        Assert.Equal(string.Empty, outputLines[^1]);
        Assert.All(outputLines[..^1], line => Assert.StartsWith("    ", line, StringComparison.Ordinal));
        Assert.Contains("    ```", outputLines);
        Assert.Contains("    @reviewers [details](https://evil.example)", outputLines);
        Assert.DoesNotContain("\u001b", result.Output, StringComparison.Ordinal);
        Assert.DoesNotContain("\u202E", result.Output, StringComparison.Ordinal);
 
        var renderedText = string.Join('\n', outputLines[..^1].Select(line => line[4..]));
        Assert.Equal(65536, renderedText.Length);
        Assert.EndsWith("x", renderedText, StringComparison.Ordinal);
    }
 
    [Theory]
    [InlineData("Authorization: Bearer bearer-secret", "Authorization: Bearer [REDACTED]")]
    [InlineData("""{"accessToken":"opaque-secret"}""", """{"accessToken":"[REDACTED]"}""")]
    [InlineData("ConnectionString=Server=db;Password=secret", "ConnectionString=[REDACTED]")]
    [InlineData("connection_string: Host=db;Username=user", "connection_string: [REDACTED]")]
    [InlineData(
        """{"connection-string":"Server=db;Password=secret"}""",
        """{"connection-string":"[REDACTED]"}""")]
    [InlineData(
        """{"ConnectionStrings":{"Default":"Endpoint=sb://example/;PrimaryKey=secret"}}""",
        """{"ConnectionStrings":{"Default":"Endpoint=sb://example/;PrimaryKey=[REDACTED]""")]
    [InlineData(
        "ConnectionStrings__sql=Server=tcp:db.example;Initial Catalog=app;User ID=sa;Encrypt=True",
        "ConnectionStrings__sql=[REDACTED]")]
    [InlineData(
        "ConnectionStrings:kafka = broker1:9092,broker2:9092;SaslUsername=svc",
        "ConnectionStrings:kafka = [REDACTED]")]
    [InlineData("Password='secret;tail';Timeout=30", "Password='[REDACTED]';Timeout=30")]
    [InlineData(
        """command --password "prefix\"secret-suffix" --verbose""",
        """command --password "[REDACTED]" --verbose""")]
    [InlineData(
        "https://user:password@example.com/path",
        "https://[REDACTED]:[REDACTED]@example.com/path")]
    [InlineData(
        "https://example.test/path?token=query-secret&mode=diagnostic",
        "https://example.test/path?token=[REDACTED]&mode=diagnostic")]
    [RequiresTools(["bash", "jq"])]
    public async Task UntrustedTextSanitizerRedactsRecognizableCredentials(string input, string expected)
    {
        var inputPath = Path.Combine(_workspace.Path, "job.log");
        var outputPath = Path.Combine(_workspace.Path, "sanitized.log");
        await File.WriteAllTextAsync(inputPath, input);
 
        var result = await RunBashScriptAsync(
            Path.Combine(RepoRoot.Path, PersistenceScriptRelativePath),
            ["sanitize-untrusted-text", inputPath, outputPath]);
 
        Assert.Equal(0, result.ExitCode);
        Assert.Equal(expected, (await File.ReadAllTextAsync(outputPath)).TrimEnd('\r', '\n'));
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task UntrustedTextSanitizerRedactsCredentialsBeforeBounding()
    {
        var inputPath = Path.Combine(_workspace.Path, "job.log");
        var outputPath = Path.Combine(_workspace.Path, "sanitized.log");
        var input =
            $"{new string('x', 3950)}-----BEGIN PRIVATE KEY-----\n{new string('k', 200)}\n-----END PRIVATE KEY-----\nExpected 42 but got 41";
        await File.WriteAllTextAsync(inputPath, input);
 
        var result = await RunBashScriptAsync(
            Path.Combine(RepoRoot.Path, PersistenceScriptRelativePath),
            ["sanitize-untrusted-text", inputPath, outputPath, "4096"]);
 
        Assert.Equal(0, result.ExitCode);
        Assert.Equal(
            $"{new string('x', 3950)}[REDACTED]\nExpected 42 but got 41",
            (await File.ReadAllTextAsync(outputPath)).TrimEnd('\r', '\n'));
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task IssueRendererTreatsCauseTextAsInertCode()
    {
        var causePath = Path.Combine(_workspace.Path, "cause.json");
        var bodyPath = Path.Combine(_workspace.Path, "issue-body.md");
        var metadataPath = Path.Combine(_workspace.Path, "issue-metadata.json");
        await File.WriteAllTextAsync(
            causePath,
            """{"id":"test-failure","type":"flaky-test","title":"[click](https://evil.example)","test_name":"Tests.`![img](https://evil.example/image.png)","error_pattern":"Failure\r# heading\n```\n@reviewers","job_ids":[1]}""");
 
        var result = await RunBashScriptAsync(
            Path.Combine(RepoRoot.Path, IssueScriptRelativePath),
            [
                causePath,
                "unused-run-context.json",
                "unused-last-success.json",
                "unused-triggering-merge.json",
                "unused-history-status.json",
                "https://github.com/microsoft/aspire/actions/runs/123",
                "pull-request",
                "42",
                "Tests",
                "| occurrence |",
                bodyPath,
                metadataPath,
            ]);
 
        Assert.Equal(0, result.ExitCode);
        Assert.Equal(
            """
            <!-- ci-failure-cause:test-failure -->
            <!-- ci-failure-cause-type:flaky-test -->
 
            ## Build Information
 
            Build: https://github.com/microsoft/aspire/actions/runs/123
            Build error leg or test failing: Tests / `` Tests.`![img](https://evil.example/image.png) ``
            Pull request: #42
 
            ## Error Message
 
                Failure
                # heading
                ```
                @reviewers
 
            ## Description
 
            ` [click](https://evil.example) `
 
            **Type**: flaky-test
 
            <!-- ci-failure-occurrences:start -->
            ## Occurrences
 
            Showing 1 most recent of 1 occurrences.
 
            | Date | Build | Job | Context |
            |------|-------|-----|----|
            | occurrence |
            <!-- ci-failure-occurrences:end -->
            """.ReplaceLineEndings("\n") + "\n",
            (await File.ReadAllTextAsync(bodyPath)).ReplaceLineEndings("\n"));
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task CommentRendererTreatsJobAndTestDiagnosticsAsInertCode()
    {
        var analysisPath = Path.Combine(_workspace.Path, "analysis.json");
        var trustedJobsPath = Path.Combine(_workspace.Path, "failed-jobs.json");
        await File.WriteAllTextAsync(
            analysisPath,
            """
            {
              "verdict": "flaky-test",
              "failed_jobs": [
                {
                  "id": 1,
                  "classification": "flaky-test",
                  "reason": "[job reason](https://evil.example)"
                }
              ],
              "failed_tests": [
                {
                  "name": "Tests.`![image](https://evil.example/image.png)",
                  "job": "Tests `\r\nLinux",
                  "error": "Failure\n# heading\n```\n@reviewers",
                  "stack_trace": "frame\n```\n[link](https://evil.example)",
                  "classification": "flaky",
                  "reason": "[test reason](https://evil.example)"
                }
              ]
            }
            """);
        await File.WriteAllTextAsync(trustedJobsPath, """[{"id":1,"name":"Tests `\r\nLinux"}]""");
 
        var result = await RunBashScriptAsync(
            Path.Combine(RepoRoot.Path, CommentScriptRelativePath),
            [analysisPath, trustedJobsPath, "https://github.com/microsoft/aspire/actions/runs/123"]);
 
        Assert.Equal(0, result.ExitCode);
        Assert.Contains(
            "- `` Tests ` Linux `` — ` [job reason](https://evil.example) ` (flaky-test)",
            result.Output,
            StringComparison.Ordinal);
        Assert.Contains(
            "- `` Tests.`![image](https://evil.example/image.png) `` in job `` Tests ` Linux ``",
            result.Output,
            StringComparison.Ordinal);
        Assert.Contains(
            "  - **Error**:\n\n        Failure\n        # heading\n        ```\n        @reviewers",
            result.Output,
            StringComparison.Ordinal);
        Assert.Contains(
            "  - **Stack Trace** (first frames):\n\n        frame\n        ```\n        [link](https://evil.example)",
            result.Output,
            StringComparison.Ordinal);
        Assert.Contains(
            "  - **Why likely flaky**: ` [test reason](https://evil.example) `",
            result.Output,
            StringComparison.Ordinal);
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task IssueOccurrenceRendererPreservesHumanTextAndKeepsNewestRowsWithinBudget()
    {
        var currentBodyPath = Path.Combine(_workspace.Path, "current-body.md");
        var outputPath = Path.Combine(_workspace.Path, "updated-body.md");
        var largeJob = new string('x', 900);
        await File.WriteAllTextAsync(
            currentBodyPath,
            $$"""
            <!-- ci-failure-cause:test-failure -->
            <!-- ci-failure-cause-type:flaky-test -->
 
            ## Operator notes
 
            Preserve this human-authored text.
 
            ## Occurrences
 
            | Date | Build | Job | Context |
            |------|-------|-----|----|
            | 2026-08-01 | [1](https://github.com/microsoft/aspire/actions/runs/1) | ` {{largeJob}}-oldest ` | main |
            | 2026-08-02 | [2](https://github.com/microsoft/aspire/actions/runs/2) | ` {{largeJob}}-middle ` | main |
            | 2026-08-03 | [3](https://github.com/microsoft/aspire/actions/runs/3) | ` {{largeJob}}-newest ` | main |
            """.ReplaceLineEndings("\r\n"));
        var newRow = $"| 2026-08-04 | [4](https://github.com/microsoft/aspire/actions/runs/4) | ` {largeJob}-new ` | main |";
 
        var result = await RunBashScriptAsync(
            Path.Combine(RepoRoot.Path, PersistenceScriptRelativePath),
            ["render-issue-occurrences", currentBodyPath, newRow, "4", outputPath, "2500"]);
 
        Assert.Equal(0, result.ExitCode);
        var outputBody = await File.ReadAllTextAsync(outputPath);
        Assert.True(new FileInfo(outputPath).Length <= 2500);
        Assert.Contains("Preserve this human-authored text.", outputBody, StringComparison.Ordinal);
        Assert.Contains("Showing 2 most recent of 4 occurrences.", outputBody, StringComparison.Ordinal);
        Assert.DoesNotContain("-oldest", outputBody, StringComparison.Ordinal);
        Assert.DoesNotContain("-middle", outputBody, StringComparison.Ordinal);
        Assert.Contains("-newest", outputBody, StringComparison.Ordinal);
        Assert.Contains("-new", outputBody, StringComparison.Ordinal);
        Assert.Contains("<!-- ci-failure-occurrences:start -->", outputBody, StringComparison.Ordinal);
        Assert.Contains("<!-- ci-failure-occurrences:end -->", outputBody, StringComparison.Ordinal);
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task MainIssueMigrationReplacesGeneratedDetailsAndPreservesOperatorNotes()
    {
        var currentBodyPath = Path.Combine(_workspace.Path, "current-body.md");
        var canonicalBodyPath = Path.Combine(_workspace.Path, "canonical-body.md");
        var outputPath = Path.Combine(_workspace.Path, "updated-body.md");
        await File.WriteAllTextAsync(
            currentBodyPath,
            """
            <!-- ci-failure-cause:main-failure -->
            <!-- ci-failure-cause-type:main-repository-breakage -->
 
            ## Build Information
 
            Build: https://github.com/microsoft/aspire/actions/runs/1
 
            ## Error Message
 
                Introduced by PR #19999
 
            ## Description
 
            ` PR #19999 broke main `
 
            **Type**: main-repository-breakage
 
            ## Operator notes
 
            Preserve this human-authored text.
 
            <!-- ci-failure-occurrences:start -->
            ## Occurrences
 
            Showing 2 most recent of 2 occurrences.
 
            | Date | Build | Job | Context |
            |------|-------|-----|----|
            | 2026-08-01 | [1](https://github.com/microsoft/aspire/actions/runs/1) | ` Build ` | main |
            | 2026-08-02 | [2](https://github.com/microsoft/aspire/actions/runs/2) | ` Build ` | main |
            <!-- ci-failure-occurrences:end -->
            """.ReplaceLineEndings("\r\n"));
        await File.WriteAllTextAsync(
            canonicalBodyPath,
            """
            <!-- ci-failure-cause:main-failure -->
            <!-- ci-failure-cause-type:main-repository-breakage -->
 
            ## Build Information
 
            Build: https://github.com/microsoft/aspire/actions/runs/2
            Affected branch: `main`
            Last successful main SHA: `successful`
            Failed main SHA: `failed`
 
            ## Error Message
 
                The main branch CI run failed. See the linked workflow run and trusted commit context above for diagnostics.
 
            ## Description
 
            ` Main branch CI failure at failed `
 
            **Type**: main-repository-breakage
 
            <!-- ci-failure-occurrences:start -->
            ## Occurrences
 
            Showing 1 most recent of 2 occurrences.
 
            | Date | Build | Job | Context |
            |------|-------|-----|----|
            | 2026-08-02 | [2](https://github.com/microsoft/aspire/actions/runs/2) | ` Build ` | main |
            <!-- ci-failure-occurrences:end -->
            """);
 
        var result = await RunBashScriptAsync(
            Path.Combine(RepoRoot.Path, PersistenceScriptRelativePath),
            ["migrate-main-issue-body", currentBodyPath, canonicalBodyPath, outputPath]);
 
        Assert.Equal(0, result.ExitCode);
        Assert.Equal(
            """
            <!-- ci-failure-cause:main-failure -->
            <!-- ci-failure-cause-type:main-repository-breakage -->
 
            ## Build Information
 
            Build: https://github.com/microsoft/aspire/actions/runs/2
            Affected branch: `main`
            Last successful main SHA: `successful`
            Failed main SHA: `failed`
 
            ## Error Message
 
                The main branch CI run failed. See the linked workflow run and trusted commit context above for diagnostics.
 
            ## Description
 
            ` Main branch CI failure at failed `
 
            **Type**: main-repository-breakage
 
            ## Operator notes
 
            Preserve this human-authored text.
 
            <!-- ci-failure-occurrences:start -->
            ## Occurrences
 
            Showing 2 most recent of 2 occurrences.
 
            | Date | Build | Job | Context |
            |------|-------|-----|----|
            | 2026-08-01 | [1](https://github.com/microsoft/aspire/actions/runs/1) | ` Build ` | main |
            | 2026-08-02 | [2](https://github.com/microsoft/aspire/actions/runs/2) | ` Build ` | main |
            <!-- ci-failure-occurrences:end -->
            """.ReplaceLineEndings("\n") + "\n",
            (await File.ReadAllTextAsync(outputPath)).ReplaceLineEndings("\n"));
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task IssueOccurrenceRendererMigratesLegacyPrHeader()
    {
        var currentBodyPath = Path.Combine(_workspace.Path, "current-body.md");
        var outputPath = Path.Combine(_workspace.Path, "updated-body.md");
        await File.WriteAllTextAsync(
            currentBodyPath,
            """
            <!-- ci-failure-cause:test-failure -->
            <!-- ci-failure-cause-type:flaky-test -->
 
            **Type**: flaky-test
 
            ## Occurrences
 
            | Date | Build | Job | PR |
            |------|-------|-----|----|
            | 2026-08-01 | [1](https://github.com/microsoft/aspire/actions/runs/1) | ` Tests ` | #123 |
            """.ReplaceLineEndings("\r\n"));
 
        var result = await RunBashScriptAsync(
            Path.Combine(RepoRoot.Path, PersistenceScriptRelativePath),
            [
                "render-issue-occurrences",
                currentBodyPath,
                "| 2026-08-02 | [2](https://github.com/microsoft/aspire/actions/runs/2) | ` Tests ` | #124 |",
                "2",
                outputPath,
            ]);
 
        Assert.Equal(0, result.ExitCode);
        Assert.Equal(
            """
            <!-- ci-failure-cause:test-failure -->
            <!-- ci-failure-cause-type:flaky-test -->
 
            **Type**: flaky-test
 
            <!-- ci-failure-occurrences:start -->
            ## Occurrences
 
            Showing 2 most recent of 2 occurrences.
 
            | Date | Build | Job | Context |
            |------|-------|-----|----|
            | 2026-08-01 | [1](https://github.com/microsoft/aspire/actions/runs/1) | ` Tests ` | #123 |
            | 2026-08-02 | [2](https://github.com/microsoft/aspire/actions/runs/2) | ` Tests ` | #124 |
            <!-- ci-failure-occurrences:end -->
            """.ReplaceLineEndings("\n") + "\n",
            (await File.ReadAllTextAsync(outputPath)).ReplaceLineEndings("\n"));
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task IssueOccurrenceRendererRejectsPrHeaderInManagedSection()
    {
        var currentBodyPath = Path.Combine(_workspace.Path, "current-body.md");
        var outputPath = Path.Combine(_workspace.Path, "updated-body.md");
        await File.WriteAllTextAsync(
            currentBodyPath,
            """
            <!-- ci-failure-cause:test-failure -->
            <!-- ci-failure-cause-type:flaky-test -->
 
            **Type**: flaky-test
 
            <!-- ci-failure-occurrences:start -->
            ## Occurrences
 
            Showing 1 most recent of 1 occurrences.
 
            | Date | Build | Job | PR |
            |------|-------|-----|----|
            | 2026-08-01 | [1](https://github.com/microsoft/aspire/actions/runs/1) | ` Tests ` | #123 |
            <!-- ci-failure-occurrences:end -->
            """.ReplaceLineEndings("\n"));
 
        var result = await RunBashScriptAsync(
            Path.Combine(RepoRoot.Path, PersistenceScriptRelativePath),
            [
                "render-issue-occurrences",
                currentBodyPath,
                "| 2026-08-02 | [2](https://github.com/microsoft/aspire/actions/runs/2) | ` Tests ` | #124 |",
                "2",
                outputPath,
            ]);
 
        Assert.Equal(2, result.ExitCode);
        Assert.False(File.Exists(outputPath));
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task IssueOccurrenceRendererDoesNotGrowWhitespaceAcrossUpdates()
    {
        var currentBodyPath = Path.Combine(_workspace.Path, "current-body.md");
        var firstOutputPath = Path.Combine(_workspace.Path, "first-output.md");
        var secondOutputPath = Path.Combine(_workspace.Path, "second-output.md");
        await File.WriteAllTextAsync(
            currentBodyPath,
            """
            <!-- ci-failure-cause:test-failure -->
            <!-- ci-failure-cause-type:flaky-test -->
 
            **Type**: flaky-test
 
            <!-- ci-failure-occurrences:start -->
            ## Occurrences
 
            Showing 1 most recent of 1 occurrences.
 
            | Date | Build | Job | Context |
            |------|-------|-----|----|
            | 2026-08-01 | [1](https://github.com/microsoft/aspire/actions/runs/1) | ` Tests ` | main |
            <!-- ci-failure-occurrences:end -->
            """.ReplaceLineEndings("\r\n"));
 
        var firstResult = await RunBashScriptAsync(
            Path.Combine(RepoRoot.Path, PersistenceScriptRelativePath),
            [
                "render-issue-occurrences",
                currentBodyPath,
                "| 2026-08-02 | [2](https://github.com/microsoft/aspire/actions/runs/2) | ` Tests ` | main |",
                "2",
                firstOutputPath,
            ]);
        var secondResult = await RunBashScriptAsync(
            Path.Combine(RepoRoot.Path, PersistenceScriptRelativePath),
            [
                "render-issue-occurrences",
                firstOutputPath,
                "| 2026-08-03 | [3](https://github.com/microsoft/aspire/actions/runs/3) | ` Tests ` | main |",
                "3",
                secondOutputPath,
            ]);
 
        Assert.Equal(0, firstResult.ExitCode);
        Assert.Equal(0, secondResult.ExitCode);
        var outputBody = await File.ReadAllTextAsync(secondOutputPath);
        Assert.Contains(
            "**Type**: flaky-test\n\n<!-- ci-failure-occurrences:start -->",
            outputBody.ReplaceLineEndings("\n"),
            StringComparison.Ordinal);
        Assert.DoesNotContain(
            "**Type**: flaky-test\n\n\n<!-- ci-failure-occurrences:start -->",
            outputBody.ReplaceLineEndings("\n"),
            StringComparison.Ordinal);
        Assert.EndsWith(
            "<!-- ci-failure-occurrences:end -->\n",
            outputBody.ReplaceLineEndings("\n"),
            StringComparison.Ordinal);
        Assert.False(
            outputBody.ReplaceLineEndings("\n").EndsWith(
                "<!-- ci-failure-occurrences:end -->\n\n",
                StringComparison.Ordinal));
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task IssueRendererRejectsBodyAbovePublicationBudget()
    {
        var causePath = Path.Combine(_workspace.Path, "cause.json");
        var bodyPath = Path.Combine(_workspace.Path, "issue-body.md");
        var metadataPath = Path.Combine(_workspace.Path, "issue-metadata.json");
        await File.WriteAllTextAsync(
            causePath,
            """{"id":"test-failure","type":"flaky-test","title":"Failure","test_name":"Tests.Flaky","error_pattern":"boom","job_ids":[1]}""");
 
        var result = await RunBashScriptAsync(
            Path.Combine(RepoRoot.Path, IssueScriptRelativePath),
            [
                causePath,
                "unused-run-context.json",
                "unused-last-success.json",
                "unused-triggering-merge.json",
                "unused-history-status.json",
                "https://github.com/microsoft/aspire/actions/runs/123",
                "pull-request",
                "42",
                "Tests",
                $"| 2026-08-04 | [123](https://github.com/microsoft/aspire/actions/runs/123) | {new string('x', 65000)} | #42 |",
                bodyPath,
                metadataPath,
            ]);
 
        Assert.Equal(2, result.ExitCode);
        Assert.Contains(
            "::warning::Rendered cause issue exceeds the 65000-byte publication budget",
            result.Output,
            StringComparison.Ordinal);
    }
 
    [Theory]
    [InlineData(0)]
    [InlineData(238)]
    [InlineData(239)]
    [RequiresTools(["bash", "jq"])]
    public async Task MainIssueRendererIgnoresLegacyTitles(int titleLength)
    {
        var causePath = Path.Combine(_workspace.Path, "cause.json");
        var runContextPath = Path.Combine(_workspace.Path, "run-context.json");
        var lastSuccessfulPath = Path.Combine(_workspace.Path, "last-successful.json");
        var triggeringMergePath = Path.Combine(_workspace.Path, "triggering-merge.json");
        var candidateHistoryStatusPath = Path.Combine(_workspace.Path, "candidate-merge-history-status.json");
        var bodyPath = Path.Combine(_workspace.Path, "issue-body.md");
        var metadataPath = Path.Combine(_workspace.Path, "issue-metadata.json");
        await File.WriteAllTextAsync(
            causePath,
            JsonSerializer.Serialize(new
            {
                id = "main-failure",
                type = "main-repository-breakage",
                title = new string('x', titleLength),
                error_pattern = "Failure",
            }));
        await File.WriteAllTextAsync(runContextPath, """{"head_sha":"failed"}""");
        await File.WriteAllTextAsync(lastSuccessfulPath, """{"head_sha":"successful"}""");
        await File.WriteAllTextAsync(triggeringMergePath, "{}");
        await File.WriteAllTextAsync(candidateHistoryStatusPath, """{"state":"available"}""");
 
        var result = await RunBashScriptAsync(
            Path.Combine(RepoRoot.Path, IssueScriptRelativePath),
            [
                causePath,
                runContextPath,
                lastSuccessfulPath,
                triggeringMergePath,
                candidateHistoryStatusPath,
                "https://github.com/microsoft/aspire/actions/runs/123",
                "main",
                "0",
                "Build",
                "| occurrence |",
                bodyPath,
                metadataPath,
            ]);
 
        Assert.Equal(0, result.ExitCode);
        using var metadata = JsonDocument.Parse(await File.ReadAllTextAsync(metadataPath));
        Assert.Equal(
            "[Main CI Failure] Main branch CI failure at failed",
            metadata.RootElement.GetProperty("title").GetString());
    }
 
    [Fact]
    [RequiresTools(["bash", "jq"])]
    public async Task CauseJobNamesUseTrustedPerCauseAttribution()
    {
        var trustedJobsPath = Path.Combine(_workspace.Path, "failed-jobs.json");
        var buildCausePath = Path.Combine(_workspace.Path, "build-cause.json");
        var testCausePath = Path.Combine(_workspace.Path, "test-cause.json");
        var multiJobCausePath = Path.Combine(_workspace.Path, "multi-job-cause.json");
        await File.WriteAllTextAsync(
            trustedJobsPath,
            """[{"id":1,"name":"Build | [Linux](https://evil.example) @reviewers `quoted`"},{"id":2,"name":"Tests\r\nWindows"}]""");
        await File.WriteAllTextAsync(
            buildCausePath,
            """{"id":"build-failure","type":"infra-failure","title":"Build failure","error_pattern":"boom","job_ids":[1]}""");
        await File.WriteAllTextAsync(
            testCausePath,
            """{"id":"test-failure","type":"flaky-test","title":"Test failure","test_name":"Tests.Flaky","error_pattern":"boom","job_ids":[2]}""");
        await File.WriteAllTextAsync(multiJobCausePath, """{"job_ids":[2,1]}""");
        var failureDataDirectory = Directory.CreateDirectory(Path.Combine(_workspace.Path, "ci-failure-data")).FullName;
        await File.WriteAllTextAsync(
            Path.Combine(failureDataDirectory, "run-context.json"),
            """{"run_scope":"main"}""");
 
        var buildResult = await RunBashScriptAsync(
            Path.Combine(RepoRoot.Path, PersistenceScriptRelativePath),
            ["cause-job-names", buildCausePath, trustedJobsPath, "display"]);
        var plainResult = await RunBashScriptAsync(
            Path.Combine(RepoRoot.Path, PersistenceScriptRelativePath),
            ["cause-job-names", multiJobCausePath, trustedJobsPath, "plain"]);
        var testResult = await RunBashScriptAsync(
            Path.Combine(RepoRoot.Path, PersistenceScriptRelativePath),
            ["cause-job-names", testCausePath, trustedJobsPath, "display"]);
        var tableResult = await RunBashScriptAsync(
            Path.Combine(RepoRoot.Path, PersistenceScriptRelativePath),
            ["cause-job-names", multiJobCausePath, trustedJobsPath, "table"]);
        var occurrenceResult = await RunBashScriptAsync(
            Path.Combine(RepoRoot.Path, PersistenceScriptRelativePath),
            [
                "add-occurrence",
                multiJobCausePath,
                "42",
                "https://github.com/microsoft/aspire/actions/runs/42",
                plainResult.Output.Trim(),
                "2026-08-31T00:00:00Z",
            ]);
 
        Assert.Equal(0, buildResult.ExitCode);
        Assert.Equal(
            "`` Build | [Linux](https://evil.example) @reviewers `quoted` ``\n",
            buildResult.Output);
        Assert.Equal(0, plainResult.ExitCode);
        Assert.Equal(
            "Tests Windows, Build | [Linux](https://evil.example) @reviewers `quoted`\n",
            plainResult.Output);
        Assert.Equal(0, testResult.ExitCode);
        Assert.Equal("` Tests Windows `\n", testResult.Output);
        Assert.Equal(0, tableResult.ExitCode);
        Assert.Equal(
            "` Tests Windows `<br>`` Build \\| [Linux](https://evil.example) @reviewers `quoted` ``\n",
            tableResult.Output);
        Assert.Equal(0, occurrenceResult.ExitCode);
        using (var occurrence = JsonDocument.Parse(occurrenceResult.Output))
        {
            Assert.Equal(
                "Tests Windows, Build | [Linux](https://evil.example) @reviewers `quoted`",
                occurrence.RootElement.GetProperty("occurrences")[0].GetProperty("job").GetString());
        }
 
        var buildBodyPath = Path.Combine(_workspace.Path, "build-body.md");
        var buildMetadataPath = Path.Combine(_workspace.Path, "build-metadata.json");
        var testBodyPath = Path.Combine(_workspace.Path, "test-body.md");
        var testMetadataPath = Path.Combine(_workspace.Path, "test-metadata.json");
        var buildIssueResult = await RunBashScriptAsync(
            Path.Combine(RepoRoot.Path, IssueScriptRelativePath),
            [
                buildCausePath,
                "unused-run-context.json",
                "unused-last-success.json",
                "unused-triggering-merge.json",
                "unused-history-status.json",
                "https://github.com/microsoft/aspire/actions/runs/123",
                "pull-request",
                "42",
                buildResult.Output.TrimEnd(),
                "| build occurrence |",
                buildBodyPath,
                buildMetadataPath,
            ]);
        var testIssueResult = await RunBashScriptAsync(
            Path.Combine(RepoRoot.Path, IssueScriptRelativePath),
            [
                testCausePath,
                "unused-run-context.json",
                "unused-last-success.json",
                "unused-triggering-merge.json",
                "unused-history-status.json",
                "https://github.com/microsoft/aspire/actions/runs/123",
                "pull-request",
                "42",
                testResult.Output.TrimEnd(),
                "| test occurrence |",
                testBodyPath,
                testMetadataPath,
            ]);
 
        Assert.Equal(0, buildIssueResult.ExitCode);
        Assert.Equal(0, testIssueResult.ExitCode);
        Assert.Contains(
            "Build error leg: `` Build | [Linux](https://evil.example) @reviewers `quoted` ``\n",
            await File.ReadAllTextAsync(buildBodyPath),
            StringComparison.Ordinal);
        Assert.Contains(
            "Build error leg or test failing: ` Tests Windows ` / ` Tests.Flaky `\n",
            await File.ReadAllTextAsync(testBodyPath),
            StringComparison.Ordinal);
    }
 
    private static string[] GetWorkflowCommandLines(string output)
        => output.ReplaceLineEndings("\n")
            .Split('\n', StringSplitOptions.RemoveEmptyEntries)
            .Where(line => line.StartsWith("::", StringComparison.Ordinal))
            .ToArray();
 
    private static string CreateCause(string id, string type, int jobId, params int[] additionalJobIds)
    {
        var testName = type == "flaky-test" ? ",\"test_name\":\"Tests.Flaky\"" : string.Empty;
 
        return $$"""{"id":"{{id}}","type":"{{type}}","title":"Failure"{{testName}},"error_pattern":"boom","job_ids":{{JsonSerializer.Serialize(new[] { jobId }.Concat(additionalJobIds))}}}""";
    }
 
    private async Task<CommandResult> RunValidationScriptAsync(string agentOutputPath)
    {
        var scriptPath = Path.Combine(RepoRoot.Path, ValidationScriptRelativePath);
        Assert.True(File.Exists(scriptPath), $"Expected validation helper at '{ValidationScriptRelativePath}'.");
 
        using var process = new Process();
        process.StartInfo.FileName = "bash";
        process.StartInfo.ArgumentList.Add(scriptPath);
        process.StartInfo.WorkingDirectory = _workspace.Path;
        process.StartInfo.RedirectStandardError = true;
        process.StartInfo.RedirectStandardOutput = true;
        process.StartInfo.UseShellExecute = false;
        process.StartInfo.Environment["GH_AW_AGENT_OUTPUT"] = agentOutputPath;
        process.StartInfo.Environment["ANALYSIS_DIR"] = Path.Combine(_workspace.Path, "ci-analysis-output");
 
        process.Start();
 
        // Read both streams concurrently to avoid deadlock when the validator emits diagnostics.
        var stdoutTask = process.StandardOutput.ReadToEndAsync();
        var stderrTask = process.StandardError.ReadToEndAsync();
        using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(30));
        await process.WaitForExitAsync(timeout.Token);
 
        return new CommandResult(process.ExitCode, await stdoutTask + await stderrTask);
    }
 
    private async Task<CommandResult> RunHistoryScriptAsync(
        string fakeGh,
        string failedRunCreatedAt,
        string outputPath,
        long failedRunId = 25)
    {
        var fakeBinDirectory = Directory.CreateDirectory(Path.Combine(_workspace.Path, "fake-bin")).FullName;
        var fakeGhPath = Path.Combine(fakeBinDirectory, "gh");
        await File.WriteAllTextAsync(fakeGhPath, fakeGh);
        if (!OperatingSystem.IsWindows())
        {
            File.SetUnixFileMode(
                fakeGhPath,
                UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute);
        }
 
        return await RunBashScriptAsync(
            Path.Combine(RepoRoot.Path, HistoryScriptRelativePath),
            ["microsoft/aspire", "137649006", failedRunCreatedAt, failedRunId.ToString(), outputPath],
            new Dictionary<string, string>
            {
                ["PATH"] = $"{fakeBinDirectory}{Path.PathSeparator}{Environment.GetEnvironmentVariable("PATH")}",
                ["GH_CALL_LOG"] = Path.Combine(_workspace.Path, "gh-calls.log"),
                ["GH_CALL_COUNT_FILE"] = Path.Combine(_workspace.Path, "gh-call-count"),
            });
    }
 
    private async Task<CommandResult> RunCandidateScriptAsync(string fakeGh, string candidatesPath, string statusPath)
    {
        var fakeBinDirectory = Directory.CreateDirectory(Path.Combine(_workspace.Path, "fake-bin")).FullName;
        var fakeGhPath = Path.Combine(fakeBinDirectory, "gh");
        await File.WriteAllTextAsync(fakeGhPath, fakeGh);
        if (!OperatingSystem.IsWindows())
        {
            File.SetUnixFileMode(
                fakeGhPath,
                UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute);
        }
 
        return await RunBashScriptAsync(
            Path.Combine(RepoRoot.Path, CandidatesScriptRelativePath),
            ["microsoft/aspire", "trusted-success", "trusted-failure", candidatesPath, statusPath],
            new Dictionary<string, string>
            {
                ["PATH"] = $"{fakeBinDirectory}{Path.PathSeparator}{Environment.GetEnvironmentVariable("PATH")}",
                ["GH_CALL_LOG"] = Path.Combine(_workspace.Path, "gh-calls.log"),
            });
    }
 
    private async Task<string> CreateFakeGhAsync(string script)
    {
        var fakeBinDirectory = Directory.CreateDirectory(Path.Combine(_workspace.Path, $"fake-bin-{Guid.NewGuid():N}")).FullName;
        var fakeGhPath = Path.Combine(fakeBinDirectory, "gh");
        await WriteExecutableAsync(fakeGhPath, script);
 
        return fakeGhPath;
    }
 
    private static async Task WriteExecutableAsync(string path, string script)
    {
        await File.WriteAllTextAsync(path, script);
        if (!OperatingSystem.IsWindows())
        {
            File.SetUnixFileMode(
                path,
                UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute);
        }
    }
 
    private static async Task WriteZipEntryAsync(ZipArchive archive, string name, string contents)
    {
        var entry = archive.CreateEntry(name);
        await using var stream = entry.Open();
        await using var writer = new StreamWriter(stream);
        await writer.WriteAsync(contents);
    }
 
    private async Task WritePersistenceFixtureAsync(
        string analysis,
        string runContext,
        string run,
        string trustedFailedJobs,
        string triggeringMerge,
        string lastSuccessfulRun,
        string candidateMerges,
        string prMetadata = "{}",
        string candidateHistoryStatus = """{"state":"available"}""")
    {
        var analysisDirectory = Directory.CreateDirectory(Path.Combine(_workspace.Path, "ci-analysis-output")).FullName;
        var failureDataDirectory = Directory.CreateDirectory(Path.Combine(_workspace.Path, "ci-failure-data")).FullName;
        await File.WriteAllTextAsync(Path.Combine(analysisDirectory, "analysis-result.json"), analysis);
        await File.WriteAllTextAsync(Path.Combine(failureDataDirectory, "run-context.json"), runContext);
        await File.WriteAllTextAsync(Path.Combine(failureDataDirectory, "run.json"), run);
        await File.WriteAllTextAsync(Path.Combine(failureDataDirectory, "failed-jobs.json"), trustedFailedJobs);
        await File.WriteAllTextAsync(Path.Combine(failureDataDirectory, "triggering-merge-pr.json"), triggeringMerge);
        await File.WriteAllTextAsync(Path.Combine(failureDataDirectory, "last-successful-main-run.json"), lastSuccessfulRun);
        await File.WriteAllTextAsync(Path.Combine(failureDataDirectory, "candidate-merges.json"), candidateMerges);
        await File.WriteAllTextAsync(Path.Combine(failureDataDirectory, "candidate-merge-history-status.json"), candidateHistoryStatus);
        await File.WriteAllTextAsync(Path.Combine(failureDataDirectory, "pr-metadata.json"), prMetadata);
    }
 
    private Task<CommandResult> RunPersistenceScriptAsync(string command, string? outputPath = null)
    {
        var arguments = new List<string>
        {
            command,
            Path.Combine(_workspace.Path, "ci-analysis-output", "analysis-result.json"),
        };
        if (outputPath is not null)
        {
            arguments.Add(outputPath);
            arguments.Add("2026-08-31T12:00:00Z");
        }
 
        return RunBashScriptAsync(
            Path.Combine(RepoRoot.Path, PersistenceScriptRelativePath),
            arguments,
            new Dictionary<string, string>
            {
                ["CI_FAILURE_DATA_DIR"] = Path.Combine(_workspace.Path, "ci-failure-data"),
            });
    }
 
    private async Task<CommandResult> RunBashScriptAsync(
        string scriptPath,
        IReadOnlyList<string> arguments,
        IReadOnlyDictionary<string, string>? environment = null)
        => await RunProcessAsync("bash", [scriptPath, .. arguments], environment);
 
    private async Task<CommandResult> RunProcessAsync(
        string fileName,
        IReadOnlyList<string> arguments,
        IReadOnlyDictionary<string, string>? environment = null,
        string? standardInput = null)
    {
        using var process = new Process();
        process.StartInfo.FileName = fileName;
        foreach (var argument in arguments)
        {
            process.StartInfo.ArgumentList.Add(argument);
        }
        process.StartInfo.WorkingDirectory = _workspace.Path;
        process.StartInfo.RedirectStandardError = true;
        process.StartInfo.RedirectStandardOutput = true;
        process.StartInfo.RedirectStandardInput = standardInput is not null;
        process.StartInfo.UseShellExecute = false;
        if (environment is not null)
        {
            foreach (var (name, value) in environment)
            {
                process.StartInfo.Environment[name] = value;
            }
        }
 
        process.Start();
        if (standardInput is not null)
        {
            await process.StandardInput.WriteAsync(standardInput);
            process.StandardInput.Close();
        }
        var stdoutTask = process.StandardOutput.ReadToEndAsync();
        var stderrTask = process.StandardError.ReadToEndAsync();
        using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(30));
        await process.WaitForExitAsync(timeout.Token);
 
        return new CommandResult(process.ExitCode, await stdoutTask + await stderrTask);
    }
 
    private async Task WriteValidationFixtureAsync(
        string analysis,
        string runContext,
        string trustedFailedJobs,
        string? causeFileName = null,
        string? cause = null,
        bool writeTrustedTestFailures = true,
        string testEvidenceState = "complete")
    {
        var analysisDirectory = Path.Combine(_workspace.Path, "ci-analysis-output");
        var failureDataDirectory = Path.Combine(_workspace.Path, "ci-failure-data");
        Directory.CreateDirectory(analysisDirectory);
        Directory.CreateDirectory(failureDataDirectory);
 
        await File.WriteAllTextAsync(Path.Combine(analysisDirectory, "analysis-result.json"), analysis);
        await File.WriteAllTextAsync(Path.Combine(failureDataDirectory, "run-context.json"), runContext);
        await File.WriteAllTextAsync(Path.Combine(failureDataDirectory, "failed-jobs.json"), trustedFailedJobs);
        await File.WriteAllTextAsync(
            Path.Combine(failureDataDirectory, "run.json"),
            """{"id":123,"html_url":"https://github.com/microsoft/aspire/actions/runs/123"}""");
        await File.WriteAllTextAsync(
            Path.Combine(failureDataDirectory, "test-evidence.json"),
            JsonSerializer.Serialize(new { state = testEvidenceState }));
        if (writeTrustedTestFailures)
        {
            var trustedTestFailures = new List<Dictionary<string, string>>();
            using var analysisDocument = JsonDocument.Parse(analysis);
            if (analysisDocument.RootElement.TryGetProperty("failed_tests", out var failedTests) &&
                failedTests.ValueKind == JsonValueKind.Array)
            {
                foreach (var failedTest in failedTests.EnumerateArray())
                {
                    if (failedTest.ValueKind == JsonValueKind.Object &&
                        failedTest.TryGetProperty("name", out var name) &&
                        name.ValueKind == JsonValueKind.String &&
                        failedTest.TryGetProperty("job", out var job) &&
                        job.ValueKind == JsonValueKind.String &&
                        failedTest.TryGetProperty("error", out var error) &&
                        error.ValueKind == JsonValueKind.String)
                    {
                        trustedTestFailures.Add(new Dictionary<string, string>
                        {
                            ["test"] = name.GetString()!,
                            ["job"] = job.GetString()!,
                            ["error"] = error.GetString()!,
                            ["stack_trace"] =
                                failedTest.TryGetProperty("stack_trace", out var stackTrace) &&
                                stackTrace.ValueKind == JsonValueKind.String
                                    ? stackTrace.GetString()!
                                    : string.Empty,
                        });
                    }
                }
            }
            await File.WriteAllTextAsync(
                Path.Combine(failureDataDirectory, "test-failures.json"),
                JsonSerializer.Serialize(trustedTestFailures));
        }
        if (causeFileName is not null && cause is not null)
        {
            await WriteCauseFilesAsync(new Dictionary<string, string> { [causeFileName] = cause });
        }
    }
 
    private async Task WriteValidationFixtureAsync(
        string analysis,
        string runContext,
        string trustedFailedJobs,
        IReadOnlyDictionary<string, string> causes)
    {
        await WriteValidationFixtureAsync(analysis, runContext, trustedFailedJobs);
        await WriteCauseFilesAsync(causes);
    }
 
    private async Task WriteCauseFilesAsync(IReadOnlyDictionary<string, string> causes)
    {
        var causesDirectory = Directory.CreateDirectory(Path.Combine(_workspace.Path, "ci-analysis-output", "causes")).FullName;
        foreach (var (fileName, cause) in causes)
        {
            await File.WriteAllTextAsync(Path.Combine(causesDirectory, fileName), cause);
        }
    }
 
    private async Task AssertValidationRejectsMismatchedCausePresenceAsync()
    {
        var result = await RunValidationScriptAsync(Path.Combine(_workspace.Path, "output.json"));
 
        Assert.NotEqual(0, result.ExitCode);
        Assert.Contains(
            "::error::Failed-job classifications and persisted cause types do not match",
            result.Output,
            StringComparison.Ordinal);
    }
 
    private async Task AssertValidationRejectsIncompatibleCauseJobAsync()
    {
        var result = await RunValidationScriptAsync(Path.Combine(_workspace.Path, "output.json"));
 
        Assert.NotEqual(0, result.ExitCode);
        Assert.Contains(
            "references an unknown or incompatible failed job",
            result.Output,
            StringComparison.Ordinal);
    }
 
    private sealed record CommandResult(int ExitCode, string Output);
}