///|
fn impact_request(request : Value) -> Value raise {
  let tasks = load_tasks(get(request, "tasks"))
  let changed = string_list(get(request, "changed_inputs"))
  let paths : Map[String, Bool] = Map([])
  for path in changed {
    if !relative_path(path) {
      raise InputError("unsafe changed input path")
    }
    paths[path.to_lower()] = true
  }
  let affected : Map[String, Bool] = Map([])
  let output : Array[Value] = []
  let unaffected : Array[String] = []
  for task in tasks {
    let inputs = task.inputs.filter(path => paths.contains(path.to_lower()))
    let upstream = task.dependencies.filter(id => affected.contains(id))
    if !inputs.is_empty() || !upstream.is_empty() {
      affected[task.id] = true
      output.push(
        record([
          ("id", String(task.id)),
          ("changed_inputs", strings(inputs)),
          ("affected_dependencies", strings(upstream)),
        ]),
      )
    } else {
      unaffected.push(task.id)
    }
  }
  record([
    ("affected", Array(output)),
    ("unaffected", strings(unaffected)),
    ("plan_only", Bool(true)),
    (
      "scope",
      String(
        "potential dependency closure; content cutoff and cache may reduce actual work",
      ),
    ),
  ])
}