name: CI

# The QUICKSTART requires Agent F's conformance suite to run in CI "from the day
# Agent A lands" — it is the architecture's tripwire. This workflow runs the
# whole workspace's checks plus the testkit's conformance suite on every push and
# pull request.

on:
  push:
    branches: [main, master]
  pull_request:
  schedule:
    # Nightly soak at 04:17 UTC.
    - cron: "17 4 * * *"

env:
  CARGO_TERM_COLOR: always
  RUSTFLAGS: -D warnings
  # Every blocking job pins an exact toolchain. With `-D warnings` and a
  # floating `stable`, a Rust release turns this gate red with no repo change —
  # which is exactly how it first failed, eight days after 1.97.1 added
  # `float_literal_f32_fallback`. Worse, the developer machine cannot install
  # arbitrary toolchains, so such a failure is unreproducible locally and the
  # local gate stops meaning anything. Pinned here to the version developed
  # against; bump it deliberately, as its own commit, with the lints it
  # introduces fixed in that commit. The nightly soak floats on `stable` and is
  # the drift detector.
  PINNED_STABLE: "1.95.0"

jobs:
  # Style and lint policy runs on exactly ONE toolchain, the pinned stable.
  #
  # It used to run on the MSRV toolchain as well, and that is not a stricter
  # gate — it is a different one. Two clippy versions have two lint sets, and
  # they can disagree in opposite directions: clippy 1.85 demands
  # `.and_then(seg_pos)` where 1.95 is silent, while 1.97 rejects a bare `2.0`
  # that 1.95 accepts. Satisfying the union is not a quality bar, it is a
  # coincidence, and no developer can reproduce it locally without installing
  # every toolchain in the matrix. Lints here; the MSRV job proves the floor
  # compiles and passes its tests, which is the only thing an MSRV can mean.
  lint:
    name: fmt / clippy / doc (pinned stable)
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install Rust (pinned stable)
        uses: dtolnay/rust-toolchain@stable
        with:
          toolchain: ${{ env.PINNED_STABLE }}
          components: rustfmt, clippy

      - name: Install GUI build dependencies
        run: |
          sudo apt-get update
          sudo apt-get install -y \
            libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev \
            libxkbcommon-dev libssl-dev

      - name: Cache cargo
        uses: Swatinem/rust-cache@v2

      - name: Formatting
        run: cargo fmt --all -- --check

      # The whole workspace, GUI crate included: on the pinned stable there is
      # no toolchain reason to split it out.
      - name: Clippy (all targets)
        run: cargo clippy --workspace --all-targets -- -D warnings

      # Deny rustdoc warnings (broken/private intra-doc links, etc.). `RUSTFLAGS`
      # does not cover rustdoc, so `RUSTDOCFLAGS` is set explicitly.
      - name: Doc warnings (workspace)
        env:
          RUSTDOCFLAGS: -D warnings
        run: cargo doc --workspace --no-deps

  # The floor, and only the floor: does the code this repository claims to
  # support on 1.85 actually build and pass its tests there? No lints — see the
  # `lint` job's comment.
  check:
    name: MSRV build + test (1.85)
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install Rust (pinned MSRV)
        uses: dtolnay/rust-toolchain@stable
        with:
          toolchain: "1.85"

      - name: Cache cargo
        uses: Swatinem/rust-cache@v2

      # The demo GUI crate (eframe/resvg) pulls `image`, which declares 1.88, so
      # it cannot build at the floor and is excluded here. It is linted in
      # `lint` and tested in `editor-gui`, both on the pinned stable.
      - name: Test (workspace)
        run: cargo test --workspace --exclude epiphany-editor-gui --all-targets

      - name: Doc tests
        run: cargo test --workspace --exclude epiphany-editor-gui --doc

  # The demo GUI crate's tests, kept as their own job so a GUI break is its own
  # attributable signal rather than a regression buried in the core crates. Its
  # eframe/resvg tree pulls `image`, which declares 1.88 — three releases above
  # the workspace floor — so it cannot run in the MSRV job. Linting for this
  # crate lives in `lint` with the rest of the workspace.
  editor-gui:
    name: editor GUI tests (pinned stable)
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install Rust (pinned stable)
        uses: dtolnay/rust-toolchain@stable
        with:
          toolchain: ${{ env.PINNED_STABLE }}

      - name: Install GUI build dependencies
        run: |
          sudo apt-get update
          sudo apt-get install -y \
            libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev \
            libxkbcommon-dev libssl-dev

      - name: Cache cargo
        uses: Swatinem/rust-cache@v2

      - name: Test (GUI)
        run: cargo test -p epiphany-editor-gui

      # Assertion-message artifact paths (`target/golden-failures/<name>/actual.png`
      # etc.) name the ephemeral runner's filesystem — a local-reproduction aid, not
      # a reviewable record on CI. This is the reviewable record: on a golden
      # mismatch, the three per-test images (actual/expected/diff) upload so a
      # reviewer can see the failure without reproducing it locally.
      - name: Upload golden failure artifacts
        if: failure()
        uses: actions/upload-artifact@v4
        with:
          name: golden-failures
          path: target/golden-failures/
          if-no-files-found: ignore

  conformance:
    name: conformance suite (testkit)
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install Rust
        uses: dtolnay/rust-toolchain@stable
        with:
          toolchain: ${{ env.PINNED_STABLE }}

      - name: Cache cargo
        uses: Swatinem/rust-cache@v2

      # The full conformance suite at scale 1: Agent A's 1,000,000-iteration
      # determinism gate, the round-trip corpus, the crash-recovery and
      # manifest-selection gates, convergence / reduction-determinism /
      # equivocation against the real epiphany-ops, and the layout round-trip.
      - name: Run conformance suite
        run: cargo run --release -p epiphany-testkit --example conformance_suite --features golden-gate 1

      # The Chapter 10 performance-budget gates (Phase 2 worklist F1), in
      # release with reduced sampling (EPIPHANY_BENCH_QUICK): the 1K and 10K
      # reduction points and both bundle budgets must pass; known-pending
      # points are documented xfails inside the benches ("F surfaces, K
      # fixes"). The heaviest (50K) reduction point runs only in the nightly
      # soak below.
      - name: Performance budget gates (quick)
        env:
          EPIPHANY_BENCH_QUICK: "1"
        run: cargo bench -p epiphany-testkit

  # Track A, Agent H's merge gate (Phase 2). A discrete job so a spelling /
  # decomposition pre-pass regression is attributable to H, not buried in the
  # workspace test run. Asserts H's PHASE2_QUICKSTART acceptance criterion: the
  # representative-corpus eligibility taxonomy (F3) and the pre-pass harness (F4 —
  # determinism, spelling correctness, decomposition reconstruction, RespellPitch
  # precedence, non-vacuity). The same gate also runs inside the conformance
  # suite's stage [7b]; this job isolates it for fast, attributable feedback.
  prepass-harness:
    name: Agent H pre-pass gate (testkit)
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install Rust
        uses: dtolnay/rust-toolchain@stable
        with:
          toolchain: ${{ env.PINNED_STABLE }}

      - name: Cache cargo
        uses: Swatinem/rust-cache@v2

      - name: H spelling + decomposition merge gate
        run: cargo test -p epiphany-testkit --test prepass

  # A longer soak, scheduled nightly, exploring a wider seed space at scale.
  # This job deliberately floats on `stable` while every blocking job is pinned:
  # it is where toolchain drift surfaces, on a schedule, instead of in a push
  # that has nothing to do with it. A red soak against a green push means Rust
  # moved, not that the change did.
  soak:
    name: conformance soak (nightly)
    if: github.event_name == 'schedule'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable
      - uses: Swatinem/rust-cache@v2
      - name: Run conformance soak
        run: cargo run --release -p epiphany-testkit --example conformance_suite 10
      # The full budget-gate run, including the minute-scale 50K reduction
      # point (a documented xfail until the O(n²) reducer fix lands).
      - name: Performance budget gates (full)
        run: cargo bench -p epiphany-testkit
