///|
pub struct GuardPipelineOutput {
  verification : VerificationResult
  route : RouteDecision
  risk : RiskScore
  state : GuardState
} derive(Eq, Debug)

///|
pub fn run_guard_pipeline(
  request : WebhookRequest,
  config : GuardConfig,
) -> GuardPipelineOutput {
  let (verification, state) = verify_request_with_state(
    request,
    config.policy,
    config.state,
  )
  let route = if verification.is_ok() {
    config.router.route(request, config.policy.provider)
  } else {
    RouteDecision::miss("verification failed")
  }
  let risk = score_result(verification)
  { verification, route, risk, state }
}

///|
pub fn pipeline_output_text(output : GuardPipelineOutput) -> String {
  let builder = StringBuilder::new()
  builder.write_string(output.verification.to_text())
  builder.write_string("\nroute=")
  if output.route.matched {
    builder.write_string(output.route.handler)
  } else {
    builder.write_string(output.route.reason)
  }
  builder.write_string("\nrisk=")
  builder.write_string(explain_risk(output.risk))
  builder.to_string()
}

///|
pub fn run_stateful_batch(
  requests : Array[WebhookRequest],
  config : GuardConfig,
) -> Array[GuardPipelineOutput] {
  let mut state = config.state
  Array::makei(requests.length(), i => {
    let output = run_guard_pipeline(requests[i], config.with_state(state))
    state = output.state
    output
  })
}

///|
pub fn pipeline_batch_text(outputs : Array[GuardPipelineOutput]) -> String {
  let builder = StringBuilder::new()
  for i in 0..