# ── Existing send infrastructure ────────────────────────────────────────

[sinks.debug]
enabled = true
type = "debug"

[sinks.http]
enabled = false
type = "http"
destination = "http://localhost:8080/webhook/000"
total_duration_of_retries = "30s"

[sources.cli]
enabled = true

[sources.cli.extractor]
type = "cli"

# ── Built-in run types (all disabled; --run <name> enables one) ──────────
[sources.testsuiterun_junit]
enabled = false
transformer_refs = [
  "ci_env_detection",
  "to_testsuiterun_junit",
  "testoutput_from_testsuiterun",
  "ci_links"
]

[sources.testsuiterun_junit.extractor]
type = "subprocess"
data_globs = ["**/TEST-*.xml", "**/*.xml"]
parser = "xml"

[sources.testsuiterun_tap]
enabled = false
transformer_refs = [
  "ci_env_detection",
  "to_testsuiterun_tap",
  "testoutput_from_testsuiterun",
  "ci_links"
]

[sources.testsuiterun_tap.extractor]
type = "subprocess"
data_globs = ["**/*.tap"]
parser = "tap"

[sources.testsuiterun_sarif]
enabled = false
transformer_refs = [
  "ci_env_detection",
  "to_testsuiterun_sarif",
  "testoutput_from_testsuiterun",
  "ci_links"
]

[sources.testsuiterun_sarif.extractor]
type = "subprocess"
data_globs = ["**/*.sarif", "**/*.sarif.json"]
parser = "json"

[sources.taskrun]
enabled = false
transformer_refs = ["ci_env_detection", "to_taskrun", "ci_links"]

[sources.taskrun.extractor]
type = "subprocess"
no_data = true

# ── CDEvent builders (named so custom configs can reference or override) ──

[transformers.to_testsuiterun_junit]
type = "vrl"
template = '''
# Converts subprocess EventSource to testSuiteRun CDEvent (JUnit XML variant)
# At this point .metadata.run.* is populated by ci_env_detection

suite_name = string(.metadata.run.suite_name) ?? "unknown"
suite_id   = string(.metadata.run.suite_id) ?? suite_name
suite_uri  = string(.metadata.run.suite_uri) ?? suite_name
subject_id = string(.metadata.run.subject_id) ?? "unknown"
source_url = string(.metadata.context.source) ?? ""
env_id     = string(.metadata.run.env_id) ?? "/local"

if .metadata.run.status == "started" {
  event_at = string(.metadata.run.started_at) ?? ""
  .body = {
    "context": { "specversion": "0.5.0", "id": "0", "source": source_url,
      "type": "dev.cdevents.testsuiterun.started.0.3.0", "timestamp": event_at },
    "subject": { "id": subject_id,
      "content": { "trigger": {"type": "schedule"},
        "environment": {"id": env_id},
        "testSuite": {"id": suite_id, "name": suite_name, "uri": suite_uri, "version": ""} } }
  }
} else {
  event_at = string(.metadata.run.finished_at) ?? ""
  # Aggregate from array of XML-parsed JUnit files in .body
  total_tests = 0; total_failed = 0; total_errors = 0; total_skipped = 0; total_time = 0.0
  if is_array(.body) {
    for_each(array!(.body)) -> |_i, file| {
      suite = object(get(file, ["testsuite"]) ?? {}) ?? {}
      total_tests   = total_tests   + (to_int(get(suite, ["@tests"])    ?? "0") ?? 0)
      total_failed  = total_failed  + (to_int(get(suite, ["@failures"]) ?? "0") ?? 0)
      total_errors  = total_errors  + (to_int(get(suite, ["@errors"])   ?? "0") ?? 0)
      total_skipped = total_skipped + (to_int(get(suite, ["@skipped"])  ?? "0") ?? 0)
      total_time    = total_time    + (to_float(get(suite, ["@time"])   ?? "0") ?? 0.0)
    }
  }
  exit_code = to_int(.metadata.run.exit_code) ?? 0
  outcome   = if total_failed + total_errors > 0 { "failure" } else if exit_code != 0 { "failure" } else { "success" }
  passed    = total_tests - total_failed - total_errors - total_skipped

  .body = {
    "context": { "specversion": "0.5.0", "id": "0", "source": source_url,
      "type": "dev.cdevents.testsuiterun.finished.0.3.0", "timestamp": event_at },
    "subject": { "id": subject_id,
      "content": { "outcome": outcome, "reason": "",
        "environment": {"id": env_id},
        "testSuite": {"id": suite_id, "name": suite_name, "uri": suite_uri, "version": ""} } },
    "customData": {
      "testsuiterun": {
        "summary": {
          "results_count": total_tests,
          "passed": passed,
          "failed": total_failed,
          "skipped": total_skipped,
          # "notes": total_notes,
          # "warnings": total_warnings,
          "errors": total_errors,
          "duration_ms": to_int(total_time * 1000),
          # "tool": tool_name,
          # "tool_version": tool_version,
          "exit_code": exit_code
        }
      }
    }
  }
}
[.]
'''

[transformers.to_testsuiterun_tap]
type = "vrl"
template = '''
# Converts subprocess EventSource to testSuiteRun CDEvent (TAP variant)
# At this point .metadata.run.* is populated by ci_env_detection

suite_name = string(.metadata.run.suite_name) ?? "unknown"
suite_id   = string(.metadata.run.suite_id) ?? suite_name
suite_uri  = string(.metadata.run.suite_uri) ?? suite_name
subject_id = string(.metadata.run.subject_id) ?? "unknown"
source_url = string(.metadata.context.source) ?? ""
env_id     = string(.metadata.run.env_id) ?? "/local"

if .metadata.run.status == "started" {
  event_at = string(.metadata.run.started_at) ?? ""
  .body = {
    "context": { "specversion": "0.5.0", "id": "0", "source": source_url,
      "type": "dev.cdevents.testsuiterun.started.0.3.0", "timestamp": event_at },
    "subject": { "id": subject_id,
      "content": { "trigger": {"type": "schedule"},
        "environment": {"id": env_id},
        "testSuite": {"id": suite_id, "name": suite_name, "uri": suite_uri, "version": ""} } }
  }
} else {
  event_at = string(.metadata.run.finished_at) ?? ""
  total_tests = 0; total_failed = 0
  if is_array(.body) {
    for_each(array!(.body)) -> |_i, result| {
      tests = array(get(result, ["tests"]) ?? []) ?? []
      total_tests = total_tests + length(tests)
      for_each(tests) -> |_j, t| {
        status = string(get(t, ["status"]) ?? "ok") ?? "ok"
        if status == "not_ok" { total_failed = total_failed + 1 }
      }
    }
  }
  exit_code = to_int(.metadata.run.exit_code) ?? 0
  outcome   = if total_failed > 0 { "failure" } else if exit_code != 0 { "failure" } else { "success" }
  passed    = total_tests - total_failed

  .body = {
    "context": { "specversion": "0.5.0", "id": "0", "source": source_url,
      "type": "dev.cdevents.testsuiterun.finished.0.3.0", "timestamp": event_at },
    "subject": { "id": subject_id,
      "content": { "outcome": outcome, "reason": "",
        "environment": {"id": env_id},
        "testSuite": {"id": suite_id, "name": suite_name, "uri": suite_uri, "version": ""} } },
    "customData": {
      "testsuiterun": {
        "summary": {
          "results_count": total_tests,
          "passed": passed,
          "failed": total_failed,
          # "skipped": total_skipped,
          # "notes": total_notes,
          # "warnings": total_warnings,
          # "errors": total_errors,
          # "duration_ms": to_int(total_time * 1000),
          # "tool": tool_name,
          # "tool_version": tool_version,
          "exit_code": exit_code
        }
      }
    }
  }
}
[.]
'''

[transformers.to_testsuiterun_sarif]
type = "vrl"
template = '''
# Converts subprocess EventSource to testSuiteRun CDEvent (SARIF JSON variant)
# At this point .metadata.run.* is populated by ci_env_detection

suite_name = string(.metadata.run.suite_name) ?? "unknown"
suite_id   = string(.metadata.run.suite_id) ?? suite_name
suite_uri  = string(.metadata.run.suite_uri) ?? suite_name
subject_id = string(.metadata.run.subject_id) ?? "unknown"
source_url = string(.metadata.context.source) ?? ""
env_id     = string(.metadata.run.env_id) ?? "/local"

if .metadata.run.status == "started" {
  event_at = string(.metadata.run.started_at) ?? ""
  .body = {
    "context": { "specversion": "0.5.0", "id": "0", "source": source_url,
      "type": "dev.cdevents.testsuiterun.started.0.3.0", "timestamp": event_at },
    "subject": { "id": subject_id,
      "content": { "trigger": {"type": "schedule"},
        "environment": {"id": env_id},
        "testSuite": {"id": suite_id, "name": suite_name, "uri": suite_uri, "version": ""} } }
  }
} else {
  event_at = string(.metadata.run.finished_at) ?? ""
  # Extract tool name/version from first run of first SARIF file
  tool_name = suite_name; tool_version = ""
  if is_array(.body) && length(array!(.body)) > 0 {
    first_doc = object(array!(.body)[0]) ?? {}
    first_runs = array(get(first_doc, ["runs"]) ?? []) ?? []
    if length(first_runs) > 0 {
      driver = object(get(first_runs[0], ["tool", "driver"]) ?? {}) ?? {}
      d_name = string(get(driver, ["name"]) ?? "") ?? ""
      if d_name != "" {
        tool_name    = d_name
        tool_version = string(get(driver, ["version"]) ?? "") ?? ""
      }
    }
  }

  # Aggregate results across all SARIF files and runs
  total_results = 0; total_failed = 0; total_warnings = 0; total_notes = 0
  if is_array(.body) {
    for_each(array!(.body)) -> |_i, doc| {
      runs = array(get(doc, ["runs"]) ?? []) ?? []
      for_each(runs) -> |_j, run| {
        results = array(get(run, ["results"]) ?? []) ?? []
        total_results = total_results + length(results)
        for_each(results) -> |_k, result| {
          level = string(get(result, ["level"]) ?? "warning") ?? "warning"
          if level == "error" {
            total_failed   = total_failed   + 1
          } else if level == "warning" {
            total_warnings = total_warnings + 1
          } else if level == "note" {
            total_notes    = total_notes    + 1
          }
        }
      }
    }
  }

  exit_code = to_int(.metadata.run.exit_code) ?? 0
  outcome   = if total_failed > 0 { "failure" } else if exit_code != 0 { "failure" } else { "success" }

  .body = {
    "context": { "specversion": "0.5.0", "id": "0", "source": source_url,
      "type": "dev.cdevents.testsuiterun.finished.0.3.0", "timestamp": event_at },
    "subject": { "id": subject_id,
      "content": { "outcome": outcome, "reason": "",
        "environment": {"id": env_id},
        "testSuite": {"id": suite_id, "name": suite_name, "uri": suite_uri, "version": ""} } },
    "customData": {
      "testsuiterun": {
        "summary": {
          "results_count": total_results,
          # "passed": passed,
          "failed": total_failed,
          # "skipped": total_skipped,
          "notes": total_notes,
          "warnings": total_warnings,
          # "errors": total_errors,
          # "duration_ms": to_int(total_time * 1000),
          "tool": tool_name,
          "tool_version": tool_version,
          "exit_code": exit_code
        }
      }
    }
  }
}
[.]
'''

[transformers.to_taskrun]
type = "vrl"
template = '''
# Converts subprocess EventSource to taskRun CDEvent (exit-code only)
# At this point .metadata.run.* is populated by ci_env_detection

task_name = string(.metadata.run.task_name) ?? "unknown"
subject_id = string(.metadata.run.subject_id) ?? "unknown"
source_url = string(.metadata.context.source) ?? ""

if .metadata.run.status == "started" {
  event_at = string(.metadata.run.started_at) ?? ""
  .body = {
    "context": { "specversion": "0.5.0", "id": "0", "source": source_url,
      "type": "dev.cdevents.taskrun.started.0.3.0", "timestamp": event_at },
    "subject": { "id": subject_id,
      "content": { "taskName": task_name } }
  }
} else {
  exit_code = to_int(.metadata.run.exit_code) ?? 0
  outcome   = if exit_code != 0 { "failure" } else { "success" }
  event_at = string(.metadata.run.finished_at) ?? ""
  .body = {
    "context": { "specversion": "0.5.0", "id": "0", "source": source_url,
      "type": "dev.cdevents.taskrun.finished.0.3.0", "timestamp": event_at },
    "subject": { "id": subject_id,
      "content": { "outcome": outcome, "taskName": task_name } }
  }
}
[.]
'''

# ── Shared transformer: testoutput.published emission ────────────────────
# Runs AFTER ci_links; emits a testoutput.published event alongside the
# testsuiterun.finished event when .metadata.run.results_url is set.
# Returns [testsuiterun_event, testoutput_event] when results_url is present,
# or [testsuiterun_event] otherwise.
[transformers.testoutput_from_testsuiterun]
type = "vrl"
template = '''
results_url = string(.metadata.run.results_url) ?? ""
event_type  = string(.body.context.type) ?? ""

if event_type == "dev.cdevents.testsuiterun.finished.0.3.0" && results_url != "" {
  source_url = string(.metadata.context.source) ?? ""
  event_at = string(.metadata.run.finished_at) ?? ""

  testoutput_event = .
  testoutput_event.body = {
    "context": { "specversion": "0.5.0", "id": "0", "source": source_url,
      "type": "dev.cdevents.testoutput.published.0.3.0", "timestamp": event_at },
    "subject": { "id": results_url,
      "content": { "outputType": "report", "format": "text/html", "uri": results_url } }
  }
  [., testoutput_event]
} else {
  [.]
}
'''

# ── Shared transformer: cross-reference links ─────────────────────────────
# Runs AFTER the CDEvent body is built; reads .metadata.run.* link keys
# set via --metadata overrides and injects them into the emitted CDEvent.
#
# Currently writes to customData.links (CDEvents ≤ 0.5 workaround).
# When CDEvents 0.6+ makes context.links stable for subject references,
# update this single transformer to write to .body.context.links instead.
#
# Referenced by testsuiterun_* sources. Custom configs can also add
# "ci_links" to any transformer_refs list, or to pipeline.transformer_refs.
#
# Supported --metadata keys:
#   tested_artifact_id   → testedAgainst artifact  (repeatable: one link per value)
#                          e.g. pkg:oci/my-app@sha256:... or pkg:maven/com.example:lib
#   tested_env_id        → testedAgainst environment (e.g. /cluster/staging)
#   tested_repo_id       → testedAgainst repository  (e.g. https://github.com/org/repo)
#   results_url          → storedAt repository        (e.g. https://ci.example.com/results/42)
[transformers.ci_links]
type = "vrl"
template = '''
links = []

# tested_artifact_id may be a string (single, backward compat) or array (multiple)
artifact_ids = if is_array(.metadata.run.tested_artifact_id) {
  array!(.metadata.run.tested_artifact_id)
} else {
  s = string(.metadata.run.tested_artifact_id) ?? ""
  if s != "" { [s] } else { [] }
}
for_each(artifact_ids) -> |_i, aid| {
  aid_str = string(aid) ?? ""
  if aid_str != "" {
    links = push(links, {
      "linkKind": "testedAgainst",
      "target": { "subject": { "type": "artifact", "id": aid_str } }
    })
  }
}

env_id = string(.metadata.run.tested_env_id) ?? ""
if env_id != "" {
  links = push(links, {
    "linkKind": "testedAgainst",
    "target": { "subject": { "type": "environment", "id": env_id } }
  })
}

repo_id = string(.metadata.run.tested_repo_id) ?? ""
if repo_id != "" {
  links = push(links, {
    "linkKind": "testedAgainst",
    "target": { "subject": { "type": "repository", "id": repo_id } }
  })
}

results_url = string(.metadata.run.results_url) ?? ""
if results_url != "" {
  links = push(links, {
    "linkKind": "storedAt",
    "target": { "subject": { "type": "testoutput", "id": results_url } }
  })
}

# Add partOf link to the parent pipeline run (when running inside a CI pipeline)
# Reconstructs the pipelinerun id using the same scheme as the CI-system event transformers:
#   github  → suite_id + "/attempts/" + attempt  (API URL, matches workflow_run transformer)
#   gitlab  → suite_uri                           (web URL, matches pipeline transformer)
#   jenkins → suite_uri                           (build URL)
ci_system = string(.metadata.run.ci_system) ?? ""
if ci_system != "" && ci_system != "local" {
  suite_id  = string(.metadata.run.suite_id)  ?? ""
  suite_uri = string(.metadata.run.suite_uri) ?? ""
  pipeline_run_id = if ci_system == "github" {
    run_parts = split(string(.metadata.run.ci_run_id) ?? "0/1", "/")
    attempt   = string(get(run_parts, [1]) ?? "1") ?? "1"
    suite_id + "/attempts/" + attempt
  } else {
    suite_uri
  }
  if pipeline_run_id != "" {
    links = push(links, {
      "linkKind": "partOf",
      "target": { "subject": { "type": "pipelinerun", "id": pipeline_run_id } }
    })
  }
}

if length(links) > 0 {
  .body.customData.links = append(array(.body.customData.links) ?? [], links)
}
[.]
'''

# ── Shared transformer: CI environment detection ─────────────────────────
# Reads CI env vars via VRL get_env_var(); enriches .metadata.run.*
# Referenced by all run types via transformer_refs = ["ci_env_detection", ...]
[transformers.ci_env_detection]
type = "vrl"
template = '''
# Detect CI system and populate .metadata.run.* using environment variables
# VRL get_env_var(name) returns Ok(value) or Err if unset.

github_actions = get_env_var("GITHUB_ACTIONS") ?? ""
gitlab_ci      = get_env_var("GITLAB_CI") ?? ""
jenkins_url    = get_env_var("JENKINS_URL") ?? ""

if github_actions == "true" {
  # https://docs.github.com/en/actions/reference/workflows-and-actions/variables
  repo         = get_env_var("GITHUB_REPOSITORY") ?? ""
  run_id_base  = get_env_var("GITHUB_RUN_ID") ?? "0"
  run_id       = join!([run_id_base, get_env_var("GITHUB_RUN_ATTEMPT") ?? "1"], "/attempts/")
  job_name     = get_env_var("GITHUB_JOB") ?? "unknown"
  workflow     = get_env_var("GITHUB_WORKFLOW") ?? ""
  ref_name     = get_env_var("GITHUB_REF_NAME") ?? ""
  server       = get_env_var("GITHUB_SERVER_URL") ?? "https://github.com"

  .metadata.run.ci_system   = "github"
  .metadata.run.ci_repo     = repo
  .metadata.run.ci_run_id   = run_id
  .metadata.run.ci_job_name = job_name
  .metadata.run.ci_workflow = workflow
  .metadata.run.suite_name  = string(.metadata.run.suite_name) ?? (repo + "/" + workflow + "/" + job_name)
  .metadata.run.task_name   = string(.metadata.run.task_name) ?? (repo + "/" + workflow + "/" + job_name)
  .metadata.run.suite_id    = string(.metadata.run.suite_id) ?? ("https://api.github.com/repos/" + repo + "/actions/runs/" + run_id_base)
  .metadata.run.suite_uri   = string(.metadata.run.suite_uri) ?? (server + "/" + repo + "/actions/runs/" + run_id_base)
  .metadata.run.env_id      = string(.metadata.run.env_id) ?? (server + "/" + repo + "/tree/" + encode_percent(ref_name))
   # 404 url, but without "/{job_name}" the url id is not unique enough, and the job_number used in url "/runs/000/job/0000" is not available as environment variable
  .metadata.run.subject_id  = string(.metadata.run.subject_id) ?? (server + "/" + repo + "/actions/runs/" + run_id + "/" + job_name)

  # Set context.source to pipeline definition URL (no run ID) if not CLI-set
  if (string(.metadata.context.source) ?? "") == "" {
    .metadata.context.source = server + "/" + repo + "/actions"
  }

} else if gitlab_ci == "true" {
  # https://docs.gitlab.com/ci/variables/predefined_variables/
  repo         = get_env_var("CI_PROJECT_PATH") ?? ""
  run_id       = get_env_var("CI_PIPELINE_ID") ?? "0"
  job_name     = get_env_var("CI_JOB_NAME") ?? "unknown"
  ref_name     = get_env_var("CI_COMMIT_REF_NAME") ?? ""
  project_id   = get_env_var("CI_PROJECT_ID") ?? "0"
  pipeline_url = get_env_var("CI_PIPELINE_URL") ?? ""
  project_url  = get_env_var("CI_PROJECT_URL") ?? ("https://gitlab.com/" + repo)
  api_url      = get_env_var("CI_API_V4_URL") ?? "https://gitlab.com/api/v4"
  job_url      = get_env_var("CI_JOB_URL") ?? (project_url + "/-/jobs/" + (get_env_var("CI_JOB_ID") ?? "0"))

  .metadata.run.ci_system   = "gitlab"
  .metadata.run.ci_repo     = repo
  .metadata.run.ci_run_id   = run_id
  .metadata.run.ci_job_name = job_name
  .metadata.run.suite_name  = string(.metadata.run.suite_name) ?? (repo + "/" + job_name)
  .metadata.run.task_name   = string(.metadata.run.task_name) ?? (repo + "/" + job_name)
  _gitlab_suite_uri         = if pipeline_url != "" { pipeline_url } else { project_url + "/-/pipelines/" + run_id }
  .metadata.run.suite_id    = string(.metadata.run.suite_id) ?? (api_url + "/projects/" + project_id + "/pipelines/" + run_id)
  .metadata.run.suite_uri   = string(.metadata.run.suite_uri) ?? _gitlab_suite_uri
  .metadata.run.env_id      = string(.metadata.run.env_id) ?? (project_url + "/-/tree/" + encode_percent(ref_name))
  .metadata.run.subject_id  = string(.metadata.run.subject_id) ?? job_url

  # Set context.source to project pipelines URL (no pipeline ID) if not CLI-set
  if (string(.metadata.context.source) ?? "") == "" {
    .metadata.context.source = project_url + "/-/pipelines"
  }

} else if jenkins_url != "" {
  job_name   = get_env_var("JOB_BASE_NAME") ?? "unknown"
  run_id     = get_env_var("BUILD_NUMBER") ?? "0"
  job_full   = get_env_var("JOB_NAME") ?? job_name
  build_url  = get_env_var("BUILD_URL") ?? ""

  .metadata.run.ci_system   = "jenkins"
  .metadata.run.ci_repo     = job_full
  .metadata.run.ci_run_id   = run_id
  .metadata.run.ci_job_name = job_name
  .metadata.run.suite_name  = string(.metadata.run.suite_name) ?? (job_full + "/" + job_name)
  .metadata.run.task_name   = string(.metadata.run.task_name) ?? (job_full + "/" + job_name)
  _jenkins_build_url        = if build_url != "" { build_url } else { jenkins_url + "job/" + job_full + "/" + run_id + "/" }
  .metadata.run.suite_id    = string(.metadata.run.suite_id) ?? (jenkins_url + "job/" + job_full + "/" + run_id + "/api/json")
  .metadata.run.suite_uri   = string(.metadata.run.suite_uri) ?? _jenkins_build_url
  .metadata.run.env_id      = string(.metadata.run.env_id) ?? (jenkins_url + "job/" + job_full + "/")
  .metadata.run.subject_id  = string(.metadata.run.subject_id) ?? (_jenkins_build_url + encode_percent(job_name))

  # Set context.source to job definition URL (no build number) if not CLI-set
  if (string(.metadata.context.source) ?? "") == "" {
    .metadata.context.source = jenkins_url + "job/" + job_full + "/"
  }

} else {
  # Generic local fallback — leave context.source unset so Processor injects default
  suite = if is_string(.metadata.run.suite_name) && .metadata.run.suite_name != "" { string!(.metadata.run.suite_name) } else { "run" }
  .metadata.run.ci_system   = "local"
  .metadata.run.ci_repo     = ""
  .metadata.run.ci_job_name = ""
  .metadata.run.suite_name  = suite
  .metadata.run.suite_id    = string(.metadata.run.suite_id) ?? ("cdviz-collector://local/" + encode_percent(suite))
  .metadata.run.suite_uri   = string(.metadata.run.suite_uri) ?? ("cdviz-collector://local/" + encode_percent(suite))
  .metadata.run.env_id      = "/local"
  .metadata.run.subject_id  = "cdviz-collector://local/" + suite
}

# Apply user --metadata overrides (injected as metadata.run.overrides by subprocess extractor)
overrides = object(.metadata.run.overrides) ?? {}
for_each(overrides) -> |k, v| {
  .metadata.run = set!(.metadata.run, [k], v)
}

[.]
'''
