///|
/// The policy-aware interpreter boundary. The narrow selected-plan API remains
/// separate so its historical exact contract keeps its original behavior.
pub fn plan_scheme_capabilities(
  unit : @data.IRUnit,
  suite : @protocol.Suite,
  policy~ : CapabilityPolicy = Strict,
  limits~ : @data.Limits = @data.Limits::default(),
) -> SchemePlan raise {
  let selected = plan_scheme(unit, suite, policy~, limits~)
  for call in selected.suite().calls {
    for argument in call.arguments {
      validate_sdk_boundary(argument)
    }
  }
  if policy is Permissive {
    let captured = @data.Registry::new(limits=selected.limits).decode(
      "ion-binary",
      { unit_id: selected.unit_id, content: Binary(selected.source), },
    )
    for entry in selected.entries {
      ignore(
        @backend.emit_selected(
          captured.distribution,
          @ir.FQName::parse(entry.name),
        ),
      )
    }
  }
  selected
}

///|
/// Check complete terminal evidence against the plan's captured public context.
/// This validates evidence; it neither executes nor authenticates a execution.
pub fn SchemePlan::validate_outcomes(
  self : SchemePlan,
  outcomes : @protocol.Outcomes,
) -> Unit raise {
  let suite = @protocol.Suite::decode(self.suite, "json")
  @protocol.validate_outcomes(outcomes, suite, self.entries, self.definitions)
  for call in outcomes.calls {
    if call.outcome is Returned(value) {
      validate_sdk_boundary(value)
    }
  }
  if @utf8.encode(outcomes.to_json().stringify()).length() > 1048576 {
    raise AdmissionInvalid("execution.output_limit")
  }
}

///|
fn scheme_capability_failure(error : Error) -> @protocol.TerminalOutcome {
  let (code, message) = match error {
    @scheme.Limit("cancelled") => return @protocol.Cancelled
    @scheme.Stage("evaluate", "execution.unsupported_reached") =>
      ("execution.unsupported_reached", "Unsupported operation reached")
    @scheme.Unbound(_) => ("scheme.unbound", "Unbound runtime reference")
    @scheme.Limit(_) => ("scheme.limit", "Runtime resource limit exceeded")
    @scheme.Invalid(_) => ("scheme.invalid", "Invalid runtime operation")
    @scheme.Unsupported(_) =>
      ("scheme.unsupported", "Unsupported Scheme operation")
    @scheme.Syntax(_, _) => ("scheme.syntax", "Invalid Scheme syntax")
    @scheme.Stage(_, _) => ("scheme.stage", "Runtime stage failed")
    @protocol.Invalid(detail) | AdmissionInvalid(detail) => {
      let code = detail.split(":").next().unwrap_or("").to_owned()
      if code.length() > 0 && code.length() <= 128 {
        (code, "Invalid typed evaluation result")
      } else {
        ("execution.evaluate", "Scheme evaluation failed")
      }
    }
    _ => ("execution.evaluate", "Scheme evaluation failed")
  }
  @protocol.Failed({ stage: Evaluate, code, message, })
}

///|
fn scheme_call_bytes(call : @protocol.CallOutcome) -> Int32 {
  let one : @protocol.Outcomes = {
    calls: [call],
    cleanup: None,
    extensions: None,
  }
  guard one.to_json() is Object(fields) && fields["calls"] is Array([value]) else {
    abort("canonical outcome projection")
  }
  @utf8.encode(value.stringify()).length()
}

///|
/// Execute each captured call and publish only fully validated typed terminals.
/// Cancellation and runtime limits share the file-load and invocation budget.
pub fn SchemePlan::evaluate_outcomes(
  self : SchemePlan,
  cancelled~ : () -> Bool = fn() { false },
) -> @protocol.Outcomes raise {
  let unit = @data.Registry::new(limits=self.limits).decode("ion-binary", {
    unit_id: self.unit_id,
    content: Binary(self.source),
  })
  let suite = @protocol.Suite::decode(self.suite, "json")
  let calls = []
  let mut retained_bytes = 0
  for call in suite.calls {
    let entry = self.entries.filter(e => e.name == call.entry)[0]
    let terminal : @protocol.TerminalOutcome = try {
      let runtime = @scheme.Runtime::new(limits={
        steps: 1000000,
        depth: 128,
        frames: 100000,
      })
      runtime.set_cancelled(cancelled)
      @backend.install(runtime, accelerators=false, model_errors=true)
      let value = try {
        let actual = if self.check_policy is Permissive {
          @backend.run_selected_in(
            runtime,
            unit.distribution,
            @ir.FQName::parse(call.entry),
            call.arguments.map(scheme_input),
          )
        } else {
          @backend.run_file_in(
            runtime,
            unit.distribution,
            @ir.FQName::parse(call.entry),
            call.arguments.map(scheme_input),
          )
        }
        scheme_output(actual, entry.output, self.definitions, 0)
      } catch {
        @scheme.Domain(code, _) => @protocol.ModelError(code)
        error => raise error
      }
      @protocol.validate_as(value, entry.output, self.definitions, output=true)
      validate_sdk_boundary(value)
      match value {
        ModelError(code) => @protocol.ModelError(code)
        _ => @protocol.Returned(value)
      }
    } catch {
      error => scheme_capability_failure(error)
    }
    let observed : @protocol.CallOutcome = { id: call.id, outcome: terminal, }
    retained_bytes += scheme_call_bytes(observed) + 1
    if retained_bytes > 1048576 {
      raise AdmissionInvalid("execution.output_limit")
    }
    calls.push(observed)
  }
  let outcomes : @protocol.Outcomes = {
    calls,
    cleanup: None,
    extensions: suite.extensions,
  }
  self.validate_outcomes(outcomes)
  outcomes
}