///|
pub(all) enum AcceptanceGate {
  CompletedWorkspace
  ExternalDataReview
  StageRetryPause
  RoutineCancellation
  AppToolContract
  PromptSkillMigration
  ImportTraceability
  ParityReadiness
  OperatorHandoffReadiness
  DecommissionArchiveReadiness
  SafetyAndRedaction
  ModelDiscovery
  ProviderFailureHandling
  RecordIntegrity
  MoondeskAppSmoke
} derive(Debug, Eq, ToJson, FromJson)

///|
pub(all) enum AcceptanceStatus {
  Pass
  NeedsWork
} derive(Debug, Eq, ToJson, FromJson)

///|
pub(all) struct AcceptanceFinding {
  gate : AcceptanceGate
  status : AcceptanceStatus
  evidence_path : String
  message : String
} derive(Debug, Eq, ToJson, FromJson)

///|
pub(all) struct AcceptanceFixtureSet {
  completed_workspace : @workspace.AnalysisWorkspace
  awaiting_workspace : @workspace.AnalysisWorkspace
  retry_result : @orchestration.TwoStageRoutineResult
  cancelled_result : @orchestration.TwoStageRoutineResult
  cancellation_report : @cancellation.CancellationReport
  provider_failure_report : @resilience.ProviderFailureReport
  record_integrity_report : @integrity.RecordIntegrityReport
  app_smoke_report : @appsmoke.MoondeskAppSmokeReport
  import_plan : @migration.LegacyImportPlan
  import_projection : @migration.LegacyImportProjection
  skill_migration_plan : @skills.SkillMigrationPlan
  parity_report : @parity.ParityReport
  comparison_report : @comparison.CutoverComparisonReport
  readiness_report : @evaluation.CutoverReadinessReport
  cutover_plan : @cutover.CutoverPlan
} derive(Debug, Eq, ToJson, FromJson)

///|
pub(all) struct AcceptanceReport {
  passed : Bool
  pass_count : Int
  needs_work_count : Int
  findings : Array[AcceptanceFinding]
  summary : String
} derive(Debug, Eq, ToJson, FromJson)

///|
fn base_suite_status() -> @model.SuiteStatusRef {
  @model.suite_status_ref(
    @model.moonsuite_suite_status_path(),
    "fixture://model/v1",
    "fixture-model",
    catalog=[
      @model.model_catalog_entry(
        "fixture-model",
        owner="moongate",
        context_window=128000,
      ),
    ],
  )
}

///|
fn fixture_diagnosis(confidence? : Int = 72) -> @domain.Stage1Diagnosis {
  @domain.stage1_diagnosis(
    TightChannel,
    Bullish,
    confidence,
    Stable,
    [AlwaysIn, H2],
    key_signals=["three closed bull bars", "second-entry continuation"],
  )
}

///|
fn fixture_decision(confidence? : Int = 64) -> @domain.Stage2Decision {
  @domain.stage2_decision(
    Wait,
    confidence,
    "fixture acceptance run; no executable order path exists",
  )
}

///|
fn workspace_fixture(
  run_id : @domain.RunId,
  source : @domain.DataSource,
) -> @workspace.AnalysisWorkspace {
  @workspace.prepare_analysis_workspace(
    @workspace.analysis_workspace_input(
      run_id,
      "acceptance fixture",
      source,
      "BTCUSDT",
      "15m",
      base_suite_status(),
      fixture_diagnosis(),
      fixture_decision(),
      limit=20,
      captured_at_ms=0L,
    ),
  )
}

///|
fn routine_fixture_request(run_id : @domain.RunId) -> @routine.RoutineRequest {
  let workspace = workspace_fixture(run_id, Fixture)
  match workspace.bundle.market_fetch.snapshot {
    Some(snapshot) =>
      @routine.routine_request(run_id, "acceptance fixture", snapshot)
    None =>
      @routine.routine_request(
        run_id,
        "acceptance fixture",
        @domain.kline_snapshot(
          "fixture:BTCUSDT:15m:1",
          Fixture,
          "BTCUSDT",
          "15m",
          0L,
          [@domain.kline_bar(0, 100.0, 102.0, 99.0, 101.0)],
        ),
      )
  }
}

///|
fn live_snapshot_fixture() -> @domain.KlineSnapshot {
  let bars : Array[@domain.KlineBar] = []
  for index in 0..<20 {
    let base = 100.0 + index.to_double()
    bars.push(
      @domain.kline_bar(
        index,
        base,
        base + 2.0,
        base - 1.0,
        base + 1.0,
        volume=1000.0 + index.to_double(),
        closed=true,
      ),
    )
  }
  @domain.kline_snapshot(
    "tradingview:BTCUSDT:15m:20",
    TradingView,
    "BTCUSDT",
    "15m",
    0L,
    bars,
  )
}

///|
fn live_rehearsal_bundle() -> @workflow.AnalysisRunBundle {
  @workflow.run_analysis_bundle_with_snapshot(
    @workflow.analysis_run_request(
      "acceptance-live",
      "acceptance live snapshot rehearsal",
      @data.market_source_request(TradingView, "BTCUSDT", "15m", limit=20),
      base_suite_status().to_gateway(),
      fixture_diagnosis(),
      fixture_decision(),
    ),
    live_snapshot_fixture(),
  )
}

///|
fn import_plan_fixture() -> @migration.LegacyImportPlan {
  @migration.prepare_legacy_import_plan(
    @migration.legacy_import_batch_input("acceptance-import", "../paa", [
      @migration.legacy_source_ref(
        "records/pending/acceptance.json",
        LegacyPendingRecord,
        "acceptance-pending",
        target_run_id="acceptance-legacy-run",
      ),
      @migration.legacy_source_ref(
        "experience/tight_channel/acceptance.json",
        LegacyExperienceEntry,
        "acceptance-experience",
      ),
      @migration.legacy_source_ref(
        "trade_records/acceptance.json",
        LegacyTradeLog,
        "acceptance-trade-log",
      ),
    ]),
  )
}

///|
pub fn acceptance_fixture_set() -> AcceptanceFixtureSet {
  let completed_workspace = workspace_fixture("acceptance-completed", Fixture)
  let awaiting_workspace = workspace_fixture("acceptance-awaiting", TradingView)
  let retry_result = @orchestration.run_two_stage_routine(
    @orchestration.two_stage_routine_input(
      routine_fixture_request("acceptance-retry"),
      @model.fixture_gateway(),
      fixture_diagnosis(confidence=120),
      fixture_decision(),
    ),
  )
  let cancelled_result = @orchestration.run_two_stage_routine(
    @orchestration.two_stage_routine_input(
      routine_fixture_request("acceptance-cancel"),
      @model.fixture_gateway(),
      fixture_diagnosis(),
      fixture_decision(),
      cancel_after_phase=Stage1Validation,
    ),
  )
  let cancellation_report = @cancellation.prepare_cancellation_report(
    cancelled_result,
  )
  let provider_failure_result = @orchestration.run_two_stage_routine(
    @orchestration.two_stage_routine_input(
      routine_fixture_request("acceptance-provider-failure"),
      @model.moongate_gateway(
        "http://127.0.0.1:15721/v1",
        "offline-model",
        health=Unavailable,
      ),
      fixture_diagnosis(),
      fixture_decision(),
    ),
  )
  let provider_failure_report = @resilience.prepare_provider_failure_report(
    @resilience.provider_failure_input(
      provider_failure_result,
      @model.normalize_provider_error("gateway timeout", status_code=503),
    ),
  )
  let record_integrity_report = @integrity.prepare_record_integrity_report(
    completed_workspace.bundle,
  )
  let app_smoke_report = @appsmoke.prepare_moondesk_app_smoke_report(
    @appsmoke.moondesk_app_smoke_input(completed_workspace, awaiting_workspace),
  )
  let import_plan = import_plan_fixture()
  let import_projection = @migration.materialize_legacy_import_projection(
    import_plan,
  )
  let skill_migration_plan = @skills.prepare_skill_migration_plan()
  let parity_report = @parity.prepare_parity_report(
    @parity.parity_report_input(completed_workspace, import_plan),
  )
  let comparison_report = @comparison.prepare_cutover_comparison_report(
    @comparison.cutover_comparison_input(
      parity_report,
      completed_workspace,
      live_rehearsal_bundle(),
    ),
  )
  let readiness_report = @evaluation.prepare_cutover_readiness_report(
    @evaluation.cutover_evaluation_input(
      completed_workspace,
      import_plan,
      parity_report,
      comparison_report,
      skill_migration_plan~,
      manifest=@pack.price_action_manifest(),
    ),
  )
  let cutover_plan = @cutover.prepare_cutover_plan(
    @cutover.cutover_plan_input(readiness_report, import_plan),
  )
  {
    completed_workspace,
    awaiting_workspace,
    retry_result,
    cancelled_result,
    cancellation_report,
    provider_failure_report,
    record_integrity_report,
    app_smoke_report,
    import_plan,
    import_projection,
    skill_migration_plan,
    parity_report,
    comparison_report,
    readiness_report,
    cutover_plan,
  }
}

///|
fn finding(
  gate : AcceptanceGate,
  status : AcceptanceStatus,
  evidence_path : String,
  message : String,
) -> AcceptanceFinding {
  { gate, status, evidence_path, message }
}

///|
fn completed_workspace_finding(
  fixtures : AcceptanceFixtureSet,
) -> AcceptanceFinding {
  let workspace = fixtures.completed_workspace
  let routine_completed = match workspace.routine_result {
    Some(result) => result.outcome is Completed
    None => false
  }
  if workspace.bundle.status is Completed &&
    workspace.job_state.outcome is Completed &&
    routine_completed &&
    workspace.book_artifact_count > 0 &&
    workspace.chart_candle_count > 0 {
    finding(
      CompletedWorkspace,
      Pass,
      "analysis-workspace",
      "fixture workspace completes with routine result, chart candles, and MoonBook artifacts",
    )
  } else {
    finding(
      CompletedWorkspace,
      NeedsWork,
      "analysis-workspace",
      "completed fixture does not prove the full analysis path",
    )
  }
}

///|
fn external_data_finding(fixtures : AcceptanceFixtureSet) -> AcceptanceFinding {
  let workspace = fixtures.awaiting_workspace
  if workspace.bundle.status is AwaitingMarketData &&
    workspace.job_state.blocking_review_count > 0 &&
    workspace.routine_result is None &&
    workspace.app_tool_manifest.surfaces.any(fn(surface) {
      surface.id == "configure-data-source"
    }) &&
    workspace.app_tool_manifest.surfaces.any(fn(surface) {
      surface.id == "submit-market-snapshot"
    }) &&
    workspace.app_tool_manifest.bindings.any(fn(binding) {
      binding.id == "market-ingest" &&
      binding.schema_id == "market-ingest-input"
    }) {
    finding(
      ExternalDataReview,
      Pass,
      "analysis-workspace.awaiting",
      "external data runs block on review and expose data-source configuration plus snapshot ingest",
    )
  } else {
    finding(
      ExternalDataReview,
      NeedsWork,
      "analysis-workspace.awaiting",
      "external data runs do not surface the expected blocking review path",
    )
  }
}

///|
fn retry_finding(fixtures : AcceptanceFixtureSet) -> AcceptanceFinding {
  let result = fixtures.retry_result
  if result.outcome is AwaitingStage1Retry &&
    result.failure_kind is Some(Stage1Invalid) &&
    result.partial_record_available &&
    result.stage_gates.any(fn(gate) { gate.retry_decision.should_retry }) {
    finding(
      StageRetryPause,
      Pass,
      "orchestration.retry",
      "invalid Stage 1 output pauses for bounded retry with partial evidence",
    )
  } else {
    finding(
      StageRetryPause,
      NeedsWork,
      "orchestration.retry",
      "Stage 1 validation failure does not produce the expected retry pause",
    )
  }
}

///|
fn cancellation_finding(fixtures : AcceptanceFixtureSet) -> AcceptanceFinding {
  let report = fixtures.cancellation_report
  if report.ready &&
    report.cancel_phase is Some(Stage1Validation) &&
    report.findings.any(fn(finding) {
      finding.check is PartialRecordPersisted && finding.status is Pass
    }) &&
    report.findings.any(fn(finding) {
      finding.check is StageBoundaryPreserved && finding.status is Pass
    }) {
    finding(
      RoutineCancellation,
      Pass,
      "cancellation-report",
      "operator cancellation after Stage 1 has a reusable partial-record proof",
    )
  } else {
    finding(
      RoutineCancellation,
      NeedsWork,
      "cancellation-report",
      "cancellation report does not prove the expected partial record boundary",
    )
  }
}

///|
fn app_tool_finding(fixtures : AcceptanceFixtureSet) -> AcceptanceFinding {
  let manifest = fixtures.completed_workspace.app_tool_manifest
  if manifest.controls.length() >= 6 &&
    manifest.required_tools.contains("run_two_stage_routine") &&
    manifest.required_tools.contains("prepare_market_refresh_plan") &&
    manifest.required_tools.contains("ingest_market_snapshot") &&
    manifest.required_tools.contains("select_experience_memories") &&
    manifest.required_tools.contains("evaluate_decision_policy") &&
    manifest.required_tools.contains("prepare_followup_session_plan") &&
    manifest.required_tools.contains("prepare_followup_response") &&
    manifest.bindings.any(fn(binding) {
      binding.id == "market-refresh" &&
      binding.schema_id == "market-refresh-plan"
    }) &&
    manifest.bindings.any(fn(binding) {
      binding.id == "raw-model-exchange" && binding.redacted
    }) &&
    manifest.bindings.any(fn(binding) {
      binding.id == "experience-memory" &&
      binding.schema_id == "experience-memory-selection"
    }) &&
    manifest.bindings.any(fn(binding) {
      binding.id == "decision-policy" &&
      binding.schema_id == "decision-policy-report"
    }) &&
    manifest.surfaces.any(fn(surface) { surface.id == "prompt-debug-panel" }) {
    finding(
      AppToolContract,
      Pass,
      "moondesk-app-tool-manifest",
      "Moondesk app-tool exposes controls, refresh readiness, data ingest, debug panes, memory selection, decision policy, follow-up session plans and responses, required tools, and redacted bindings",
    )
  } else {
    finding(
      AppToolContract,
      NeedsWork,
      "moondesk-app-tool-manifest",
      "Moondesk app-tool manifest is missing a required operator contract",
    )
  }
}

///|
fn skill_migration_finding(
  fixtures : AcceptanceFixtureSet,
) -> AcceptanceFinding {
  let plan = fixtures.skill_migration_plan
  if plan.source_count == 32 &&
    plan.mapped_count == plan.source_count &&
    plan.review_count == 0 &&
    plan.catalog.assets.length() > 0 &&
    plan.sources.any(fn(source) {
      source.target_book_path == "skills/market-diagnosis/core.md"
    }) &&
    plan.sources.any(fn(source) {
      source.target_book_path == "wiki/strategy-notes/major-trend-reversal.md"
    }) {
    finding(
      PromptSkillMigration,
      Pass,
      "skill-migration-plan",
      "PA prompt sources map into MoonBook skills, strategy notes, and concept references",
    )
  } else {
    finding(
      PromptSkillMigration,
      NeedsWork,
      "skill-migration-plan",
      "PA prompt source migration is incomplete or under review",
    )
  }
}

///|
fn import_finding(fixtures : AcceptanceFixtureSet) -> AcceptanceFinding {
  let plan = fixtures.import_plan
  let projection = fixtures.import_projection
  if plan.ready_count == 3 &&
    plan.review_count == 0 &&
    plan.skipped_count == 0 &&
    plan.write_plan.artifacts.length() == 4 &&
    projection.record_count == 3 &&
    projection.review_count == 0 &&
    projection.records.all(fn(record) {
      record.source_path.contains("../paa/") &&
      record.replay_anchor_path.contains(record.trace_id)
    }) {
    finding(
      ImportTraceability,
      Pass,
      "legacy-import-plan",
      "analysis, experience, and trade-log sources map to inspectable MoonBook records with replay anchors",
    )
  } else {
    finding(
      ImportTraceability,
      NeedsWork,
      "legacy-import-plan",
      "legacy import fixture does not preserve expected inspectable traceability",
    )
  }
}

///|
fn parity_readiness_finding(
  fixtures : AcceptanceFixtureSet,
) -> AcceptanceFinding {
  if fixtures.parity_report.review_count == 0 &&
    fixtures.parity_report.untraced_case_count == 0 &&
    fixtures.parity_report.fixture_case_count ==
    fixtures.parity_report.cases.length() &&
    fixtures.comparison_report.ready &&
    fixtures.readiness_report.ready {
    finding(
      ParityReadiness,
      Pass,
      "parity-report",
      "parity report has traced PA Agent fixtures and cutover readiness passes",
    )
  } else {
    finding(
      ParityReadiness,
      NeedsWork,
      "parity-report",
      "parity, fixture traceability, or readiness has a remaining review gate",
    )
  }
}

///|
fn operator_handoff_finding(
  fixtures : AcceptanceFixtureSet,
) -> AcceptanceFinding {
  let manifest = fixtures.completed_workspace.app_tool_manifest
  let workflow_ready = manifest.id == "moondesk-price-action" &&
    manifest.mount_path == "apps/moondesk-price-action" &&
    manifest.surfaces.any(fn(surface) {
      surface.id == "run-analysis" && surface.enabled
    }) &&
    manifest.surfaces.any(fn(surface) {
      surface.id == "start-follow-up" && surface.enabled
    }) &&
    manifest.surfaces.any(fn(surface) {
      surface.id == "replay-run" && surface.enabled
    }) &&
    manifest.surfaces.any(fn(surface) {
      surface.id == "export-run" && surface.enabled
    }) &&
    manifest.required_tools.contains("run_two_stage_routine") &&
    manifest.required_tools.contains("ingest_market_snapshot") &&
    manifest.redacted_binding_count >= 3
  if fixtures.readiness_report.ready &&
    fixtures.comparison_report.ready &&
    fixtures.cutover_plan.ready_to_cutover &&
    workflow_ready {
    finding(
      OperatorHandoffReadiness,
      Pass,
      "operator-handoff-report",
      "acceptance evidence proves the Moondesk launch path, workflow surfaces, comparison report, and cutover plan",
    )
  } else {
    finding(
      OperatorHandoffReadiness,
      NeedsWork,
      "operator-handoff-report",
      "operator handoff evidence is incomplete",
    )
  }
}

///|
fn decommission_archive_finding(
  fixtures : AcceptanceFixtureSet,
) -> AcceptanceFinding {
  let cutover = fixtures.cutover_plan
  let parity = fixtures.parity_report
  let comparison = fixtures.comparison_report
  let projection = fixtures.import_projection
  let import_ready = projection.record_count > 0 &&
    projection.review_count == 0 &&
    projection.records.all(fn(record) {
      record.source_path.contains("../paa/") &&
      record.replay_anchor_path.contains(record.trace_id)
    })
  let known_difference_ready = parity.intentional_difference_count > 0 &&
    parity.untraced_case_count == 0 &&
    parity.cases.any(fn(item) {
      item.expectation is IntentionalDifference &&
      item.evidence_refs.any(fn(ref_item) {
        ref_item.kind is KnownDifferenceNote
      })
    })
  let rollback_ready = cutover.rollback_days >= 1 &&
    cutover.rollback_plan.any(fn(step) {
      step.kind is MonitorRollbackWindow && step.path == "../paa"
    }) &&
    comparison.cases.any(fn(item) {
      item.kind is LegacyReadOnlyRollback && item.status is ComparisonPass
    })
  if cutover.ready_to_cutover &&
    import_ready &&
    known_difference_ready &&
    rollback_ready {
    finding(
      DecommissionArchiveReadiness,
      Pass,
      "decommission-report",
      "acceptance evidence proves imported records, known differences, read-only rollback, and archive readiness",
    )
  } else {
    finding(
      DecommissionArchiveReadiness,
      NeedsWork,
      "decommission-report",
      "decommission archive evidence is incomplete",
    )
  }
}

///|
fn safety_finding(fixtures : AcceptanceFixtureSet) -> AcceptanceFinding {
  let workspace = fixtures.completed_workspace
  if workspace.safety_report.ok &&
    workspace.app_tool_manifest.redacted_binding_count >= 3 &&
    workspace.safety_report.findings.any(fn(finding) {
      finding.kind is DecisionPolicyBoundary && finding.status is Pass
    }) &&
    !workspace.replay_plan.live_market_required {
    finding(
      SafetyAndRedaction,
      Pass,
      "safety-report",
      "safety, decision policy, public export redaction, app debug redaction, and replay evidence checks pass",
    )
  } else {
    finding(
      SafetyAndRedaction,
      NeedsWork,
      "safety-report",
      "safety or redaction acceptance evidence is incomplete",
    )
  }
}

///|
fn model_discovery_finding(
  fixtures : AcceptanceFixtureSet,
) -> AcceptanceFinding {
  let gateway = fixtures.completed_workspace.plan.gateway
  if gateway.default_model != "" &&
    gateway.openai_base_url != "" &&
    !gateway.usage.quota_exhausted &&
    gateway.contracts.all(fn(contract) { contract.ready }) {
    finding(
      ModelDiscovery,
      Pass,
      "suite-status-ref",
      "MoonGate model gateway includes base URL, model, quota state, and ready integration contracts",
    )
  } else {
    finding(
      ModelDiscovery,
      NeedsWork,
      "suite-status-ref",
      "MoonGate model discovery evidence is incomplete or unavailable",
    )
  }
}

///|
fn provider_failure_finding(
  fixtures : AcceptanceFixtureSet,
) -> AcceptanceFinding {
  let report = fixtures.provider_failure_report
  if report.ready &&
    report.error_kind is ProviderUnavailable &&
    report.retryable &&
    report.findings.any(fn(finding) {
      finding.check is PartialRecordPreserved && finding.status is Pass
    }) {
    finding(
      ProviderFailureHandling,
      Pass,
      "provider-failure-report",
      "provider timeout normalizes to retryable unavailable and preserves partial run evidence",
    )
  } else {
    finding(
      ProviderFailureHandling,
      NeedsWork,
      "provider-failure-report",
      "provider failure handling does not prove normalized retryable partial evidence",
    )
  }
}

///|
fn record_integrity_finding(
  fixtures : AcceptanceFixtureSet,
) -> AcceptanceFinding {
  let report = fixtures.record_integrity_report
  if report.ready &&
    report.artifact_count > 0 &&
    report.findings.any(fn(finding) {
      finding.check is AnalysisRecordSelfContained && finding.status is Pass
    }) &&
    report.findings.any(fn(finding) {
      finding.check is ReplayAnchorsRecord && finding.status is Pass
    }) {
    finding(
      RecordIntegrity,
      Pass,
      "record-integrity-report",
      "MoonBook analysis record is self-contained, committed, replayable, and exportable",
    )
  } else {
    finding(
      RecordIntegrity,
      NeedsWork,
      "record-integrity-report",
      "MoonBook record integrity evidence is incomplete",
    )
  }
}

///|
fn app_smoke_finding(fixtures : AcceptanceFixtureSet) -> AcceptanceFinding {
  let report = fixtures.app_smoke_report
  if report.ready &&
    report.findings.any(fn(finding) {
      finding.check is CompletedOperatorWorkflow && finding.status is Pass
    }) &&
    report.findings.any(fn(finding) {
      finding.check is ExternalSnapshotIngest && finding.status is Pass
    }) &&
    report.findings.any(fn(finding) {
      finding.check is MoonBookBackedState && finding.status is Pass
    }) &&
    report.findings.any(fn(finding) {
      finding.check is RedactedDebugEvidence && finding.status is Pass
    }) {
    finding(
      MoondeskAppSmoke,
      Pass,
      "moondesk-app-smoke-report",
      "Moondesk app-tool smoke proves completed, external ingest, redacted debug, and MoonBook-backed operator paths",
    )
  } else {
    finding(
      MoondeskAppSmoke,
      NeedsWork,
      "moondesk-app-smoke-report",
      "Moondesk app-tool smoke evidence is incomplete",
    )
  }
}

///|
fn count_status(
  findings : Array[AcceptanceFinding],
  status : AcceptanceStatus,
) -> Int {
  findings.fold(init=0, fn(count, item) {
    if item.status == status {
      count + 1
    } else {
      count
    }
  })
}

///|
pub fn prepare_acceptance_report(
  fixtures? : AcceptanceFixtureSet = acceptance_fixture_set(),
) -> AcceptanceReport {
  let findings = [
    completed_workspace_finding(fixtures),
    external_data_finding(fixtures),
    retry_finding(fixtures),
    cancellation_finding(fixtures),
    app_tool_finding(fixtures),
    skill_migration_finding(fixtures),
    import_finding(fixtures),
    parity_readiness_finding(fixtures),
    operator_handoff_finding(fixtures),
    decommission_archive_finding(fixtures),
    safety_finding(fixtures),
    model_discovery_finding(fixtures),
    provider_failure_finding(fixtures),
    record_integrity_finding(fixtures),
    app_smoke_finding(fixtures),
  ]
  let needs_work_count = count_status(findings, NeedsWork)
  let pass_count = count_status(findings, Pass)
  let passed = needs_work_count == 0
  {
    passed,
    pass_count,
    needs_work_count,
    findings,
    summary: if passed {
      "acceptance passed \{pass_count}/\{findings.length()} gates"
    } else {
      "acceptance has \{needs_work_count} gate(s) needing work"
    },
  }
}