Feature: Validation pipeline
  As a plan executor
  I want a validation pipeline that runs validations deterministically
  So that required failures block and informational failures are reported

  Background:
    Given a validation pipeline test environment

  # ── Empty pipeline ─────────────────────────────────────────────

  Scenario: Empty validation list returns passing summary
    Given an empty list of validation commands
    When the validation pipeline runs
    Then the validation summary total is 0
    And all required validations passed

  # ── Single required validation ─────────────────────────────────

  Scenario: Single required validation passes
    Given a required vp command "check-syntax" on resource "repo-main"
    And the vp executor returns passed for "check-syntax"
    When the validation pipeline runs
    Then the validation summary total is 1
    And the validation summary required_passed is 1
    And all required validations passed

  Scenario: Single required validation fails and blocks
    Given a required vp command "check-syntax" on resource "repo-main"
    And the vp executor returns failed for "check-syntax" with message "Syntax error on line 42"
    When the validation pipeline runs
    Then the validation summary total is 1
    And the validation summary required_failed is 1
    And not all required validations passed

  # ── Single informational validation ────────────────────────────

  Scenario: Single informational validation passes
    Given an informational vp command "lint-check" on resource "repo-main"
    And the vp executor returns passed for "lint-check"
    When the validation pipeline runs
    Then the validation summary total is 1
    And the validation summary informational_passed is 1
    And all required validations passed

  Scenario: Single informational validation fails but does not block
    Given an informational vp command "lint-check" on resource "repo-main"
    And the vp executor returns failed for "lint-check" with message "Linting warnings found"
    When the validation pipeline runs
    Then the validation summary total is 1
    And the validation summary informational_failed is 1
    And all required validations passed

  # ── Multiple validations sorted by resource/mode/name ──────────

  Scenario: Multiple validations are sorted by resource name then mode then name
    Given a required vp command "z-check" on resource "beta-repo"
    And a required vp command "a-check" on resource "alpha-repo"
    And an informational vp command "b-lint" on resource "alpha-repo"
    And the vp executor returns passed for all validations
    When the validation pipeline runs
    Then the validation results are sorted by resource then mode then name
    And the validation summary total is 3

  # ── Timeout handling ───────────────────────────────────────────

  Scenario: Validation that exceeds timeout is marked as timed out
    Given a required vp command "slow-check" on resource "repo-main" with timeout 0.2
    And the vp executor will timeout for "slow-check"
    When the validation pipeline runs
    Then the vp result for "slow-check" has timed_out true
    And the vp result for "slow-check" has passed false
    And the vp result for "slow-check" has error containing "TimeoutError"

  # ── Error normalisation ────────────────────────────────────────

  Scenario: Executor returning non-dict output is normalised to failure
    Given a required vp command "broken-check" on resource "repo-main"
    And the vp executor returns non-dict output for "broken-check"
    When the validation pipeline runs
    Then the vp result for "broken-check" has passed false
    And the vp result for "broken-check" has message containing "non-dict"

  Scenario: Executor missing passed key defaults to false
    Given a required vp command "partial-check" on resource "repo-main"
    And the vp executor returns dict without passed key for "partial-check"
    When the validation pipeline runs
    Then the vp result for "partial-check" has passed false

  Scenario: Executor raising exception is captured as error
    Given a required vp command "error-check" on resource "repo-main"
    And the vp executor raises an exception for "error-check"
    When the validation pipeline runs
    Then the vp result for "error-check" has passed false
    And the vp result for "error-check" has error containing "RuntimeError"

  # ── Required + informational mixed results ─────────────────────

  Scenario: Mixed required and informational with all required passing
    Given a required vp command "req-check" on resource "repo-main"
    And an informational vp command "info-lint" on resource "repo-main"
    And the vp executor returns passed for "req-check"
    And the vp executor returns failed for "info-lint" with message "Info warning"
    When the validation pipeline runs
    Then the validation summary required_passed is 1
    And the validation summary informational_failed is 1
    And all required validations passed

  Scenario: Mixed validations where required fails blocks
    Given a required vp command "req-check" on resource "repo-main"
    And an informational vp command "info-lint" on resource "repo-main"
    And the vp executor returns failed for "req-check" with message "Required failed"
    And the vp executor returns passed for "info-lint"
    When the validation pipeline runs
    Then the validation summary required_failed is 1
    And the validation summary informational_passed is 1
    And not all required validations passed

  # ── Summary counts verification ────────────────────────────────

  Scenario: Summary counts are accurate for multiple validations
    Given a required vp command "req-a" on resource "repo-1"
    And a required vp command "req-b" on resource "repo-1"
    And an informational vp command "info-a" on resource "repo-1"
    And an informational vp command "info-b" on resource "repo-1"
    And the vp executor returns passed for "req-a"
    And the vp executor returns failed for "req-b" with message "Failed"
    And the vp executor returns passed for "info-a"
    And the vp executor returns failed for "info-b" with message "Info fail"
    When the validation pipeline runs
    Then the validation summary total is 4
    And the validation summary required_passed is 1
    And the validation summary required_failed is 1
    And the validation summary informational_passed is 1
    And the validation summary informational_failed is 1

  # ── Grouping by resource ───────────────────────────────────────

  Scenario: Validation results can be grouped by resource
    Given a required vp command "check-a" on resource "repo-alpha"
    And a required vp command "check-b" on resource "repo-beta"
    And the vp executor returns passed for all validations
    When the validation pipeline runs
    Then the results grouped by resource have 2 groups
    And the vp resource group "repo-alpha" has 1 result
    And the vp resource group "repo-beta" has 1 result

  # ── Read-only resource guard ───────────────────────────────────

  Scenario: Skip validation on read-only resource
    Given a required vp command "write-check" on resource "readonly-repo" having resource_id "RO001"
    And the vp resource "RO001" is marked as read-only
    And the vp executor returns passed for all validations
    When the validation pipeline runs
    Then the vp result for "write-check" has passed true
    And the vp result for "write-check" has message containing "read-only"

  # ── ValidationSummary all_required_passed property ─────────────

  Scenario: ValidationSummary all_required_passed is true when no required failures
    Given a validation summary with 0 required failures
    Then the summary all_required_passed property is true

  Scenario: ValidationSummary all_required_passed is false when required failures exist
    Given a validation summary with 2 required failures
    Then the summary all_required_passed property is false

  # ── Pipeline with max_workers=1 ────────────────────────────────

  Scenario: Pipeline with max_workers=1 executes sequentially
    Given a required vp command "seq-a" on resource "repo-1"
    And a required vp command "seq-b" on resource "repo-1"
    And the vp executor returns passed for all validations
    And the pipeline max_workers is set to 1
    When the validation pipeline runs
    Then the validation summary total is 2
    And all required validations passed

  # ── Validation with data payload ───────────────────────────────

  Scenario: Validation result includes data payload
    Given a required vp command "data-check" on resource "repo-main"
    And the vp executor returns passed with data for "data-check"
    When the validation pipeline runs
    Then the vp result for "data-check" has data key "detail"

  # ── run_for_plan integration ───────────────────────────────────

  Scenario: run_for_plan stores summary in plan metadata
    Given a required vp command "plan-check" on resource "repo-main"
    And the vp executor returns passed for "plan-check"
    When the validation pipeline runs for plan with metadata
    Then the plan metadata contains validation_summary
    And the plan metadata validation_summary total is 1

  # ── Normalisation edge cases ───────────────────────────────────

  Scenario: Executor returns non-bool passed value is normalised to false
    Given a required vp command "nonbool-check" on resource "repo-main"
    And the vp executor returns non-bool passed for "nonbool-check"
    When the validation pipeline runs
    Then the vp result for "nonbool-check" has passed false

  Scenario: Executor returns non-string message is coerced to string
    Given a required vp command "nonstr-msg" on resource "repo-main"
    And the vp executor returns non-string message for "nonstr-msg"
    When the validation pipeline runs
    Then the vp result for "nonstr-msg" has passed true

  Scenario: Executor returns non-dict data is wrapped in raw key
    Given a required vp command "nondict-data" on resource "repo-main"
    And the vp executor returns non-dict data for "nondict-data"
    When the validation pipeline runs
    Then the vp result for "nondict-data" has data key "raw"

  # ── Stdout/stderr capture ──────────────────────────────────────

  Scenario: Validation that prints to stdout has output captured
    Given a required vp command "stdout-check" on resource "repo-main"
    And the vp executor prints to stdout for "stdout-check"
    When the validation pipeline runs
    Then the vp result for "stdout-check" has data key "_captured_stdout"

  Scenario: Validation that prints to stderr has output captured
    Given a required vp command "stderr-check" on resource "repo-main"
    And the vp executor prints to stderr for "stderr-check"
    When the validation pipeline runs
    Then the vp result for "stderr-check" has data key "_captured_stderr"

  # ── Duration tracking ────────────────────────────────────────────

  Scenario: Validation result has positive duration
    Given a required vp command "dur-check" on resource "repo-main"
    And the vp executor returns passed for "dur-check"
    When the validation pipeline runs
    Then the vp result for "dur-check" has positive duration_ms

  # ── Concurrent pipeline stdout/stderr restoration ─────────────────

  @tdd_issue @tdd_issue_7623
  Scenario: Concurrent pipelines restore global streams after execution
    Given two concurrent validation pipelines are prepared
    When both pipelines run concurrently using a barrier
    Then sys.stdout is not a _ThreadLocalStream after both pipelines finish
    And sys.stderr is not a _ThreadLocalStream after both pipelines finish
    And each pipeline captured its own stdout output independently
