///|
pub struct DatasetKey[Scale] {
  scale : Scale
  dataset_id : Int32
}

///|
pub fn[Scale] DatasetKey::new(
  scale : Scale,
  dataset_id : Int32,
) -> DatasetKey[Scale] {
  { scale, dataset_id, }
}

///|
pub struct MeasurementKey {
  dataset_id : Int32
  repetition_id : Int32
  block_id : Int32
}

///|
pub fn MeasurementKey::new(
  dataset_id : Int32,
  repetition_id : Int32,
  block_id : Int32,
) -> MeasurementKey {
  { dataset_id, repetition_id, block_id, }
}

///|
pub(all) enum ExperimentDesign {
  FixedDatasetRepeatedMeasurements
  MultipleDatasetsSingleMeasurement
  HierarchicalDatasetsAndRepeats
}

///|
/// Stable snake_case tag, used by `protocol_canonical_encoding` and the JSONL
/// `protocol` object.
pub fn ExperimentDesign::text(self : ExperimentDesign) -> String {
  match self {
    FixedDatasetRepeatedMeasurements => "fixed_dataset_repeated_measurements"
    MultipleDatasetsSingleMeasurement => "multiple_datasets_single_measurement"
    HierarchicalDatasetsAndRepeats => "hierarchical_datasets_and_repeats"
  }
}

///|
pub struct GenerationContext[Scale] {
  seed : UInt64
  suite_id : String
  case_id : String
  dataset_key : DatasetKey[Scale]
  generator_id : String
  generator_version : String
}

///|
pub fn[Scale] GenerationContext::new(
  seed : UInt64,
  suite_id : String,
  case_id : String,
  dataset_key : DatasetKey[Scale],
  generator_id : String,
  generator_version : String,
) -> GenerationContext[Scale] {
  { seed, suite_id, case_id, dataset_key, generator_id, generator_version, }
}

///|
pub(all) enum SetupFrequency {
  PerRun
  PerDataset
  PerImplementation
  PerSample
  PerBatch
  PerIteration
}

///|
pub(all) enum SetupTiming {
  ExcludedFromMeasurement
  IncludedInMeasurement
}

///|
pub(all) enum WorkspaceScope {
  RunWorkspace
  DatasetWorkspace
  ImplementationWorkspace
  SampleWorkspace
  BatchWorkspace
  OperationWorkspace
}

///|
pub struct SetupPolicy {
  frequency : SetupFrequency
  timing : SetupTiming
  workspace_scope : WorkspaceScope
}

///|
pub fn SetupPolicy::new(
  frequency : SetupFrequency,
  timing : SetupTiming,
  workspace_scope : WorkspaceScope,
) -> SetupPolicy {
  { frequency, timing, workspace_scope, }
}

///|
pub(all) enum BatchPolicy {
  PerImplementation
  SharedBatchSize
}

///|
/// Stable snake_case tag, used by `protocol_canonical_encoding` and the JSONL
/// `protocol` object.
pub fn BatchPolicy::text(self : BatchPolicy) -> String {
  match self {
    PerImplementation => "per_implementation"
    SharedBatchSize => "shared_batch_size"
  }
}

///|
pub struct CalibrationProtocol {
  target_batch_time_us : Double
  min_batch_iterations : Int32
  max_batch_iterations : Int32
  max_sample_time_us : Double
  batch_policy : BatchPolicy
}

///|
pub fn CalibrationProtocol::new(
  target_batch_time_us : Double,
  min_batch_iterations : Int32,
  max_batch_iterations : Int32,
  max_sample_time_us : Double,
  batch_policy : BatchPolicy,
) -> CalibrationProtocol {
  {
    target_batch_time_us,
    min_batch_iterations,
    max_batch_iterations,
    max_sample_time_us,
    batch_policy,
  }
}

///|
pub enum IntervalMode {
  ExploratoryInterval
  ConfirmatoryInterval
}

///|
pub fn confirmatory_interval() -> IntervalMode {
  ConfirmatoryInterval
}

///|
pub fn exploratory_interval() -> IntervalMode {
  ExploratoryInterval
}

///|
pub(all) enum OrderPolicy {
  BalancedBlocks(UInt64)
  FixedOrder
}

///|
pub(all) enum OutlierPolicy {
  ReportOnly
  TukeyFence
  MADTrim
}

///|
/// Stable snake_case tag, used by `protocol_canonical_encoding` and the JSONL
/// `protocol` object.
pub fn OutlierPolicy::text(self : OutlierPolicy) -> String {
  match self {
    ReportOnly => "report_only"
    TukeyFence => "tukey_fence"
    MADTrim => "mad_trim"
  }
}

///|
pub(all) enum ValidationCoverage {
  EveryMeasurement
  EveryDataset
  ConfirmatoryOnly
}

///|
/// Stable snake_case tag, used by `protocol_canonical_encoding` and the JSONL
/// `protocol` object.
pub fn ValidationCoverage::text(self : ValidationCoverage) -> String {
  match self {
    EveryMeasurement => "every_measurement"
    EveryDataset => "every_dataset"
    ConfirmatoryOnly => "confirmatory_only"
  }
}

///|
pub struct RunProtocol {
  experiment_design : ExperimentDesign
  warmup_iterations : Int32
  warmup_time_us : Double?
  calibration : CalibrationProtocol
  practical_delta_pct : Double
  order_policy : OrderPolicy
  outlier_policy : OutlierPolicy
  validation_coverage : ValidationCoverage
  exploratory_samples : Int32
  confirmatory_samples : Int32
  repeats_per_dataset : Int32
}

///|
pub fn RunProtocol::new(
  experiment_design : ExperimentDesign,
  warmup_iterations : Int32,
  warmup_time_us : Double?,
  calibration : CalibrationProtocol,
  practical_delta_pct : Double,
  order_policy : OrderPolicy,
  outlier_policy : OutlierPolicy,
  validation_coverage : ValidationCoverage,
  exploratory_samples : Int32,
  confirmatory_samples : Int32,
  repeats_per_dataset~ : Int32 = 1,
) -> RunProtocol {
  {
    experiment_design,
    warmup_iterations,
    warmup_time_us,
    calibration,
    practical_delta_pct,
    order_policy,
    outlier_policy,
    validation_coverage,
    exploratory_samples,
    confirmatory_samples,
    repeats_per_dataset,
  }
}

///|
pub(all) enum ExecutionTarget {
  Native
  Js
  Wasm
  WasmGc
  Llvm
  Custom(String)
}

///|
pub fn ExecutionTarget::text(self : ExecutionTarget) -> String {
  match self {
    Native => "native"
    Js => "js"
    Wasm => "wasm"
    WasmGc => "wasm-gc"
    Llvm => "llvm"
    Custom(target) => target
  }
}

///|
fn execution_target_equal(a : ExecutionTarget, b : ExecutionTarget) -> Bool {
  match (a, b) {
    (Native, Native)
    | (Js, Js)
    | (Wasm, Wasm)
    | (WasmGc, WasmGc)
    | (Llvm, Llvm) => true
    (Custom(left), Custom(right)) => left == right
    _ => false
  }
}

///|
pub struct SemanticEnvironment {
  target : ExecutionTarget
  toolchain : String
  compiler_flags : String
  dtype_abi : String
}

///|
pub fn SemanticEnvironment::new(
  target : ExecutionTarget,
  toolchain : String,
  compiler_flags : String,
  dtype_abi : String,
) -> SemanticEnvironment {
  { target, toolchain, compiler_flags, dtype_abi, }
}

///|
pub struct PerformanceEnvironment {
  runtime : String
  cpu : String
  gc : String
  concurrency : Int32
  clock : String
  device : String
  frequency_policy : String
}

///|
pub fn PerformanceEnvironment::new(
  runtime : String,
  cpu : String,
  gc : String,
  concurrency : Int32,
  clock : String,
  device~ : String = "host",
  frequency_policy~ : String = "uncontrolled",
) -> PerformanceEnvironment {
  { runtime, cpu, gc, concurrency, clock, device, frequency_policy, }
}

///|
pub struct ProvenanceEnvironment {
  os : String
  hostname : String
  timestamp : String
  revision : String
  run_id : String
}

///|
pub fn ProvenanceEnvironment::new(
  os : String,
  hostname : String,
  timestamp : String,
  revision : String,
  run_id : String,
) -> ProvenanceEnvironment {
  { os, hostname, timestamp, revision, run_id, }
}

///|
pub struct EnvironmentSnapshot {
  semantic : SemanticEnvironment
  performance : PerformanceEnvironment
  provenance : ProvenanceEnvironment
}

///|
pub fn EnvironmentSnapshot::new(
  semantic : SemanticEnvironment,
  performance : PerformanceEnvironment,
  provenance : ProvenanceEnvironment,
) -> EnvironmentSnapshot {
  { semantic, performance, provenance, }
}

///|
pub(all) enum ValidationStatus {
  Valid
  Invalid(String)
  Skipped(String)
  ExpectedDifference(String)
  Unsupported(String)
  InfrastructureFailure(String)
}

///|
pub(all) enum ExecutionOutcome[Value] {
  Value(Value)
  Unsupported(String)
  ParseFailure(String)
  RaisedFlags(Value, Array[String])
  Trapped(String, Array[String])
  Aborted(Int32, String)
  Timeout(Int32, String)
  ExpectedDifference(String)
}

///|
pub fn[Value] ExecutionOutcome::value(value : Value) -> ExecutionOutcome[Value] {
  Value(value)
}

///|
pub fn[Value] ExecutionOutcome::raised_flags(
  value : Value,
  flags : Array[String],
) -> ExecutionOutcome[Value] {
  RaisedFlags(value, flags)
}

///|
pub fn[Value] ExecutionOutcome::kind(self : ExecutionOutcome[Value]) -> String {
  match self {
    Value(_) => "value"
    Unsupported(_) => "unsupported"
    ParseFailure(_) => "parse_failure"
    RaisedFlags(_, _) => "raised_flags"
    Trapped(_, _) => "trapped"
    Aborted(_, _) => "aborted"
    Timeout(_, _) => "timeout"
    ExpectedDifference(_) => "expected_difference"
  }
}

///|
pub fn[Value] ExecutionOutcome::flags(
  self : ExecutionOutcome[Value],
) -> Array[String] {
  match self {
    RaisedFlags(_, flags) | Trapped(_, flags) => flags
    _ => []
  }
}

///|
pub fn[Value] ExecutionOutcome::value_option(
  self : ExecutionOutcome[Value],
) -> Value? {
  match self {
    Value(value) | RaisedFlags(value, _) => Some(value)
    _ => None
  }
}

///|
pub struct OperationResult[Value, Context] {
  outcome : ExecutionOutcome[Value]
  next_context : Context?
  stdout : String
  stderr : String
  exit_code : Int32?
}

///|
pub fn[Value, Context] OperationResult::new(
  outcome : ExecutionOutcome[Value],
  next_context : Context?,
  stdout~ : String = "",
  stderr~ : String = "",
  exit_code? : Int32,
) -> OperationResult[Value, Context] {
  { outcome, next_context, stdout, stderr, exit_code, }
}

///|
pub fn[Value, Context] OperationResult::completed(
  value : Value,
  next_context : Context,
) -> OperationResult[Value, Context] {
  OperationResult::new(Value(value), Some(next_context))
}

///|
pub struct CaseDescriptor {
  operation : String
  operands : Array[String]
  context : String
  rounding : String
}

///|
pub fn CaseDescriptor::new(
  operation : String,
  operands : Array[String],
  context : String,
  rounding : String,
) -> CaseDescriptor {
  { operation, operands, context, rounding, }
}

///|
pub struct ReplaySpec {
  command : String
  arguments : Array[String]
  timeout_ms : Int32
}

///|
pub fn ReplaySpec::new(
  command : String,
  arguments : Array[String],
  timeout_ms~ : Int32 = 5000,
) -> ReplaySpec {
  { command, arguments, timeout_ms, }
}

///|
pub(all) struct ValidationEvidence {
  case_id : String
  dataset_id : Int32
  step_id : Int32
  operation : String
  operands : Array[String]
  context : String
  rounding : String
  expected : String
  actual : String
  expected_kind : String
  actual_kind : String
  expected_flags : Array[String]
  actual_flags : Array[String]
  trap : String
  stderr : String
  exit_code : Int32?
  fingerprint : String
  implementation_version : String
  replay : ReplaySpec
}

///|
pub struct Validation {
  status : ValidationStatus
  oracle_id : String
  implementation_id : String
  scale_text : String
  evidence : ValidationEvidence?
  measurement : MeasurementKey?
}

///|
pub fn Validation::new(
  status : ValidationStatus,
  oracle_id : String,
  implementation_id : String,
  scale_text : String,
  measurement? : MeasurementKey,
) -> Validation {
  {
    status,
    oracle_id,
    implementation_id,
    scale_text,
    evidence: None,
    measurement,
  }
}

///|
pub fn Validation::detailed(
  status : ValidationStatus,
  oracle_id : String,
  implementation_id : String,
  scale_text : String,
  evidence : ValidationEvidence,
  measurement? : MeasurementKey,
) -> Validation {
  {
    status,
    oracle_id,
    implementation_id,
    scale_text,
    evidence: Some(evidence),
    measurement,
  }
}

///|
pub struct ValidationFailure {
  validation : Validation
  seed : UInt64
  scale_text : String
  original_fingerprint : String
  minimal_fingerprint : String
  shrink_path : Array[String]
  minimal_input : String
}

///|
pub fn ValidationFailure::new(
  validation : Validation,
  seed : UInt64,
  scale_text : String,
  original_fingerprint : String,
  minimal_fingerprint : String,
  shrink_path : Array[String],
  minimal_input : String,
) -> ValidationFailure {
  {
    validation,
    seed,
    scale_text,
    original_fingerprint,
    minimal_fingerprint,
    shrink_path,
    minimal_input,
  }
}

///|
pub struct Observation {
  case_id : String
  implementation_id : String
  implementation_version : String
  dataset_id : Int32
  repetition_id : Int32
  block_id : Int32
  phase : ObservationPhase
  raw_elapsed_us : Double
  iterations : Int32
  batch_sink : BatchSinkStatus
  setup_timing : SetupTiming
  valid : Bool
  setup_frequency : SetupFrequency?
  workspace_scope : WorkspaceScope?
  /// Text of the dataset's scale, as produced by the case's `scale_text`.
  /// Empty when the producer did not record it.
  scale_text : String
}

///|
pub(all) enum ObservationPhase {
  Exploratory
  Confirmatory
}

///|
pub fn ObservationPhase::text(self : ObservationPhase) -> String {
  match self {
    Exploratory => "exploratory"
    Confirmatory => "confirmatory"
  }
}

///|
pub(all) enum BatchSinkStatus {
  Kept
  Discarded(String)
}

///|
pub fn BatchSinkStatus::text(self : BatchSinkStatus) -> String {
  match self {
    Kept => "kept"
    Discarded(reason) => "discarded:" + reason
  }
}

///|
pub fn Observation::new(
  case_id : String,
  implementation_id : String,
  implementation_version : String,
  dataset_id : Int32,
  repetition_id : Int32,
  block_id : Int32,
  phase : ObservationPhase,
  raw_elapsed_us : Double,
  iterations : Int32,
  batch_sink : BatchSinkStatus,
  setup_timing : SetupTiming,
  valid : Bool,
  setup_frequency? : SetupFrequency,
  workspace_scope? : WorkspaceScope,
  scale_text~ : String = "",
) -> Observation {
  {
    case_id,
    implementation_id,
    implementation_version,
    dataset_id,
    repetition_id,
    block_id,
    phase,
    raw_elapsed_us,
    iterations,
    batch_sink,
    setup_timing,
    valid,
    setup_frequency,
    workspace_scope,
    scale_text,
  }
}

///|
pub struct CalibrationEvent {
  implementation_id : String
  dataset_id : Int32
  batch_iterations : Int32
  elapsed_us : Double
  target_elapsed_us : Double
  retries : Int32
}

///|
pub fn CalibrationEvent::new(
  implementation_id : String,
  dataset_id : Int32,
  batch_iterations : Int32,
  elapsed_us : Double,
  target_elapsed_us : Double,
  retries : Int32,
) -> CalibrationEvent {
  {
    implementation_id,
    dataset_id,
    batch_iterations,
    elapsed_us,
    target_elapsed_us,
    retries,
  }
}

///|
/// Outcome of one run.
///
/// `run_id` is built by the runner with `run_identity`: case id, protocol
/// identity, run seed, and the provenance `run_id` and `timestamp` of the
/// environment. `protocol` and `seed` record what the run was executed with,
/// so a JSONL record can be audited and re-analysed; both are `None` for
/// summaries built without them.
pub struct RunSummary {
  run_id : String
  observation_count : Int32
  validation_count : Int32
  calibration_count : Int32
  complete : Bool
  artifact_location : String?
  passed_count : Int32
  failed_count : Int32
  unsupported_count : Int32
  expected_difference_count : Int32
  measurement_validation_count : Int32
  environment : EnvironmentSnapshot?
  protocol : RunProtocol?
  seed : UInt64?
}

///|
pub fn RunSummary::new(
  run_id : String,
  observation_count : Int32,
  validation_count : Int32,
  calibration_count : Int32,
  complete : Bool,
  artifact_location : String?,
  passed_count~ : Int32 = 0,
  failed_count~ : Int32 = 0,
  unsupported_count~ : Int32 = 0,
  expected_difference_count~ : Int32 = 0,
  measurement_validation_count~ : Int32 = 0,
  environment? : EnvironmentSnapshot,
  protocol? : RunProtocol,
  seed? : UInt64,
) -> RunSummary {
  {
    run_id,
    observation_count,
    validation_count,
    calibration_count,
    complete,
    artifact_location,
    passed_count,
    failed_count,
    unsupported_count,
    expected_difference_count,
    measurement_validation_count,
    environment,
    protocol,
    seed,
  }
}

///|
pub struct ScaleBoundary[Scale] {
  below : Scale
  at_or_above : Scale
}

///|
pub fn[Scale] ScaleBoundary::new(
  below : Scale,
  at_or_above : Scale,
) -> ScaleBoundary[Scale] {
  { below, at_or_above, }
}

///|
pub struct Region[Key, Choice] {
  lower : Key?
  upper : Key?
  choice : Choice
  evidence_ids : Array[String]
}

///|
pub fn[Key, Choice] Region::new(
  lower : Key?,
  upper : Key?,
  choice : Choice,
  evidence_ids : Array[String],
) -> Region[Key, Choice] {
  { lower, upper, choice, evidence_ids, }
}

///|
pub struct ParetoPoint[Key, Choice] {
  key : Key
  choice : Choice
  primary : Double
  secondary : Double
}

///|
pub fn[Key, Choice] ParetoPoint::new(
  key : Key,
  choice : Choice,
  primary : Double,
  secondary : Double,
) -> ParetoPoint[Key, Choice] {
  { key, choice, primary, secondary, }
}

///|
pub(all) enum DeploymentPolicy[Key, Choice] {
  Global(Choice)
  Piecewise(Array[Region[Key, Choice]])
  Lookup(Array[(Key, Choice)])
  Pareto(Array[ParetoPoint[Key, Choice]])
  Fallback(Choice, String)
}

///|
pub enum CrossoverResult[Scale] {
  Found(ScaleBoundary[Scale], String, Array[String])
  NoCrossover(String, Array[String])
  NonMonotonic(Array[String])
  Inconclusive(String, Array[String])
}

///|
pub fn[Scale] CrossoverResult::found(
  boundary : ScaleBoundary[Scale],
  policy : String,
  evidence : Array[String],
) -> CrossoverResult[Scale] {
  Found(boundary, policy, evidence)
}

///|
pub fn[Scale] CrossoverResult::no_crossover(
  reason : String,
  evidence : Array[String],
) -> CrossoverResult[Scale] {
  NoCrossover(reason, evidence)
}

///|
pub fn[Scale] CrossoverResult::non_monotonic(
  evidence : Array[String],
) -> CrossoverResult[Scale] {
  NonMonotonic(evidence)
}

///|
pub fn[Scale] CrossoverResult::inconclusive(
  reason : String,
  evidence : Array[String],
) -> CrossoverResult[Scale] {
  Inconclusive(reason, evidence)
}

///|
/// Exact text of a `Double`: `0x` and the 16 hex digits of its IEEE 754 bit
/// pattern. Independent of decimal formatting, distinguishes `-0.0` from
/// `0.0`, and is the same on every backend.
fn canonical_double(value : Double) -> String {
  "0x" + hex16(value.reinterpret_as_uint64())
}

///|
fn hex16(value : UInt64) -> String {
  let buffer = StringBuilder()
  for shift = 60; shift >= 0; shift = shift - 4 {
    let nibble = ((value >> shift) & 15UL).to_int()
    let code = if nibble < 10 { 48 + nibble } else { 87 + nibble }
    buffer.write_char(code.unsafe_to_char())
  }
  buffer.to_string()
}

///|
/// The `RunProtocol` fields in canonical order as `(key, value)` pairs.
///
/// Every field appears exactly once, nested fields with a dotted key. Values
/// are decimal integers, `canonical_double` texts, `none` / `some(...)` for
/// options, and `text()` tags for enums; `OrderPolicy` is `fixed_order` or
/// `balanced_blocks()`. A new `RunProtocol` field is one more line here.
fn protocol_canonical_fields(protocol : RunProtocol) -> Array[(String, String)] {
  let calibration = protocol.calibration
  [
    ("experiment_design", protocol.experiment_design.text()),
    ("warmup_iterations", protocol.warmup_iterations.to_string()),
    (
      "warmup_time_us",
      match protocol.warmup_time_us {
        None => "none"
        Some(value) => "some(" + canonical_double(value) + ")"
      },
    ),
    (
      "calibration.target_batch_time_us",
      canonical_double(calibration.target_batch_time_us),
    ),
    (
      "calibration.min_batch_iterations",
      calibration.min_batch_iterations.to_string(),
    ),
    (
      "calibration.max_batch_iterations",
      calibration.max_batch_iterations.to_string(),
    ),
    (
      "calibration.max_sample_time_us",
      canonical_double(calibration.max_sample_time_us),
    ),
    ("calibration.batch_policy", calibration.batch_policy.text()),
    ("practical_delta_pct", canonical_double(protocol.practical_delta_pct)),
    (
      "order_policy",
      match protocol.order_policy {
        FixedOrder => "fixed_order"
        BalancedBlocks(seed) => "balanced_blocks(" + seed.to_string() + ")"
      },
    ),
    ("outlier_policy", protocol.outlier_policy.text()),
    ("validation_coverage", protocol.validation_coverage.text()),
    ("exploratory_samples", protocol.exploratory_samples.to_string()),
    ("confirmatory_samples", protocol.confirmatory_samples.to_string()),
    ("repeats_per_dataset", protocol.repeats_per_dataset.to_string()),
  ]
}

///|
/// Canonical, unambiguous ASCII encoding of every `RunProtocol` field:
/// `mmkp_2` followed by `;=` for each field in a fixed order.
///
/// Keys are fixed and values never contain `;` or `=`, so two protocols have
/// the same encoding exactly when every field is equal (doubles compared by
/// bit pattern). `protocol_identity` is a digest of this text.
pub fn protocol_canonical_encoding(protocol : RunProtocol) -> String {
  let buffer = StringBuilder()
  buffer.write_string(ProtocolVersion::V2.identifier())
  for field in protocol_canonical_fields(protocol) {
    let (key, value) = field
    buffer.write_char(';')
    buffer.write_string(key)
    buffer.write_char('=')
    buffer.write_string(value)
  }
  buffer.to_string()
}

///|
/// 64-bit FNV-1a over the code units of an ASCII string.
fn fnv1a64(text : String) -> UInt64 {
  let mut hash = 0xCBF29CE484222325UL
  for index = 0; index < text.length(); index = index + 1 {
    hash = (hash ^ text[index].to_int().to_uint64()) * 0x100000001B3UL
  }
  hash
}

///|
/// Identity of a run protocol: `mmkp_2:<16 lowercase hex digits>`, the 64-bit
/// FNV-1a digest of `protocol_canonical_encoding(protocol)`.
///
/// It covers every `RunProtocol` field, including the calibration, the
/// `BalancedBlocks` seed and the optional warm-up time. Equal protocols get
/// equal identities; protocols that differ in any field get different ones
/// (up to 64-bit digest collisions). Use `protocol_canonical_encoding` or the
/// JSONL `protocol` object to see the fields behind an identity.
pub fn protocol_identity(protocol : RunProtocol) -> String {
  ProtocolVersion::V2.identifier() +
  ":" +
  hex16(fnv1a64(protocol_canonical_encoding(protocol)))
}

///|
fn escape_run_component(text : String) -> String {
  text.replace_all(old="%", new="%25").replace_all(old="|", new="%7C")
}

///|
/// `RunSummary.run_id` of one run: five components joined by `|`, in order
///
/// 1. the case id,
/// 2. `protocol_identity(protocol)`,
/// 3. the run seed in decimal,
/// 4. the provenance `run_id` of the environment,
/// 5. the provenance `timestamp` of the environment.
///
/// `%` and `|` inside the case id, provenance run id and timestamp are written
/// as `%25` and `%7C`, so the components can be recovered by splitting on `|`.
/// Two runs of the same case and protocol get different ids when their seeds,
/// provenance run ids or timestamps differ.
pub fn run_identity(
  case_id : String,
  protocol : RunProtocol,
  seed : UInt64,
  provenance : ProvenanceEnvironment,
) -> String {
  [
    escape_run_component(case_id),
    protocol_identity(protocol),
    seed.to_string(),
    escape_run_component(provenance.run_id),
    escape_run_component(provenance.timestamp),
  ].join("|")
}

///|
pub fn artifact_identity(
  case_id : String,
  implementation_id : String,
  dataset_id : Int32,
  protocol : RunProtocol,
) -> String {
  ArtifactVersion::V1.identifier() +
  ":" +
  case_id +
  ":" +
  implementation_id +
  ":" +
  dataset_id.to_string() +
  ":" +
  protocol_identity(protocol)
}

///|
pub fn environment_compatible(
  a : EnvironmentSnapshot,
  b : EnvironmentSnapshot,
) -> Bool {
  execution_target_equal(a.semantic.target, b.semantic.target) &&
  a.semantic.toolchain == b.semantic.toolchain &&
  a.semantic.compiler_flags == b.semantic.compiler_flags &&
  a.semantic.dtype_abi == b.semantic.dtype_abi &&
  a.performance.runtime == b.performance.runtime &&
  a.performance.cpu == b.performance.cpu &&
  a.performance.gc == b.performance.gc &&
  a.performance.concurrency == b.performance.concurrency &&
  a.performance.clock == b.performance.clock &&
  a.performance.device == b.performance.device &&
  a.performance.frequency_policy == b.performance.frequency_policy
}