///|
pub(all) struct VerificationResult {
  reports : Array[@execution.Report]
  committed : Bool
  publication_error : String?
}

///|
/// A committed destination can still require publication cleanup/recovery.
pub fn VerificationResult::successful(self : VerificationResult) -> Bool {
  self.committed &&
  self.publication_error is None &&
  self.reports.all(r => r.successful())
}

///|
/// Verify every required target and dispose all leases before acquiring a lock.
/// Plain execute/verify remains publication-free; this is an explicit host action.
pub fn verify_and_publish(
  plans : Array[@execution.Plan],
  provider : @execution.Provider,
  evaluator : @execution.Evaluator,
  publisher : ContentPublisher,
  destination : String,
  cancelled~ : () -> Bool = fn() { false },
  observer~ : @observe.Observer = @observe.Observer::noop(),
) -> VerificationResult raise {
  if plans.is_empty() ||
    @workspace.normalize(destination) != destination ||
    destination == "." ||
    !publisher.write_bytes ||
    publisher.guarantee is HostManaged {
    raise @config.Invalid("verification.invalid_publication")
  }
  let first = plans[0]
  let snapshot = invocation_snapshot(first.library.artifacts)
  let suite = first.suite.encode("ion-binary")
  let targets = []
  for plan in plans {
    if targets.contains(plan.target) ||
      !provider.targets.contains(plan.target) ||
      invocation_snapshot(plan.library.artifacts) != snapshot ||
      plan.suite.encode("ion-binary") != suite {
      raise @config.Invalid("verification.matrix_identity")
    }
    targets.push(plan.target)
  }
  let reports = plans.map(plan => {
    @execution.run_report(
      plan,
      provider,
      evaluator=Some(evaluator),
      observer~,
      cancelled~,
    )
  })
  if !@execution.matrix_successful(targets, reports) || cancelled() {
    return {
      reports,
      committed: false,
      publication_error: Some("verification.failed"),
    }
  }
  if plans.any(plan => {
      invocation_snapshot(plan.library.artifacts) != snapshot ||
      plan.suite.encode("ion-binary") != suite
    }) {
    raise @config.Invalid("verification.source_changed")
  }
  let receipt = @protocol.record([
    ("profile", @ion.IonValue::string("morphir-verification-v1")),
    (
      "source_identity",
      @ion.IonValue::string(@identity.sha256(@utf8.encode(snapshot))),
    ),
    ("suite_identity", @ion.IonValue::string(@identity.sha256(suite))),
    (
      "required_targets",
      @ion.IonValue::list(targets.map(@ion.IonValue::string)),
    ),
    ("reports", @ion.IonValue::list(reports.map(r => r.to_ion()))),
  ])
  let artifacts = first.library.artifacts.map(a => @engine.ContentArtifact::{
    path: @workspace.join(destination, a.path),
    source: first.unit.id,
    content: match a.content {
      Text(s) => @data.Text(s)
      Binary(b) => @data.Binary(b)
    },
    format: "source",
  })
  let receipt_path = @workspace.join(destination, "verification.ionb")
  if artifacts.any(a => a.path == receipt_path) {
    raise @config.Invalid("verification.receipt_collision")
  }
  artifacts.push({
    path: receipt_path,
    source: first.unit.id,
    content: @data.Binary(@ion_binary.write_binary([receipt])),
    format: "ion-binary",
  })
  let mut transaction : ContentPublication? = None
  let mut committed = false
  let mut publication_error = None
  try {
    if cancelled() {
      raise @config.Invalid("Cancelled")
    }
    let publication = (publisher.begin)([destination])
    transaction = Some(publication)
    for artifact in artifacts {
      (publication.stage)(artifact)
    }
    if cancelled() {
      raise @config.Invalid("Cancelled")
    }
    (publication.commit)(destination)
    committed = true
  } catch {
    error => publication_error = Some(error_message(error))
  }
  if transaction is Some(publication) {
    (publication.abort)() catch {
      error =>
        publication_error = Some(
          publication_error.unwrap_or("") +
          " verification.cleanup_failed: " +
          error_message(error),
        )
    }
  }
  { reports, committed, publication_error, }
}