melinoe 0.10.0

Zero-sized, branded, multi-token phantom capabilities for compile-time data-access and thread-synchronization proofs (a generalized evolution of GhostCell) for the Mnemosyne memory ecosystem.
Documentation
name: CI

# This repository had no test workflow. `book-pages.yml`, `msrv.yml` and
# `rust-release.yml` were the whole of its CI, so 127 native tests and 31
# doctests were committed, maintained, and never run by anything but a person
# choosing to run them. All of them pass; nothing was watching.
#
# `msrv.yml` already builds at the 1.81 floor, so that is not repeated here.

on:
  push:
    branches: [main]
  pull_request:

permissions:
  contents: read

concurrency:
  # Pull requests share one group per ref and cancel superseded runs; default-
  # branch runs get one group per commit, since GitHub supersedes a pending run
  # in a shared group whatever `cancel-in-progress` says.
  group: melinoe-ci-${{ github.event_name == 'pull_request' && github.ref || github.sha }}
  cancel-in-progress: ${{ github.event_name == 'pull_request' }}

env:
  CARGO_TERM_COLOR: always
  CARGO_INCREMENTAL: "0"
  # `rust-toolchain.toml` pins 1.97.0, so every cargo call here uses it whatever
  # the action installs. Without this input the action defaults to `stable` and
  # rustup then fetches 1.97.0 as a second toolchain on first use, with the
  # action's `components:` installed for the one that does not run.
  RUST_TOOLCHAIN: 1.97.0

jobs:
  # Public-surface compatibility, informational on pull requests: the
  # detected change class is visible while the change is still in
  # review. The blocking comparison runs on release.
  # Atlas ATLAS-SEMVER-GATE-FLEETWIDE-2026-08-28.
  semver:
    name: SemVer gate
    uses: ryancinsight/atlas/.github/workflows/semver-gate.yml@da9d549bc50dbb9c4e1d18dca8f37d17059098cb
    with:
      package: melinoe
      rust-toolchain: 1.97.0
    permissions:
      contents: read

  verify:
    name: Verify
    runs-on: ubuntu-latest
    timeout-minutes: 20
    steps:
      - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
        with:
          persist-credentials: false
      - uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable
        with:
          toolchain: ${{ env.RUST_TOOLCHAIN }}
          components: clippy, rustfmt
      - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.8.1
        with:
          save-if: ${{ github.ref == 'refs/heads/main' }}
      - uses: taiki-e/install-action@ea4c0b50b0d394ee91c5bb68a6a295b0556feb8e # nextest
        with:
          tool: nextest

      # Cheapest first, so a formatting slip costs seconds rather than a build.
      - name: Format
        run: cargo fmt --all -- --check

      # `std` is a superset of `alloc`, and the crate's whole point is that the
      # branded types work without either. A default-features build would never
      # exercise that, so the no-default-features check is not optional here.
      - name: Build without default features
        run: cargo check --locked --no-default-features

      - name: Clippy
        run: cargo clippy --locked --all-targets --all-features -- -D warnings

      - name: Native tests
        run: cargo nextest run --locked --all-features

      - name: Documentation tests
        run: cargo test --locked --doc --all-features

      - name: Documentation
        env:
          RUSTDOCFLAGS: -D warnings
        run: cargo doc --locked --no-deps --all-features

  # The README pins soundness two ways: `compile_fail` doctests, which `verify`
  # already runs, and Miri (Stacked Borrows + data-race detection). Nothing ran
  # the second one, so the claim was unfalsifiable in CI. This job makes it
  # falsifiable.
  #
  # Miri needs nightly; the crate's own `rust-toolchain.toml` pins stable, so
  # the toolchain is selected per-invocation rather than through the repo file.
  #
  # `PROPTEST_CASES` is bounded deliberately. `tests/partition.rs` carries three
  # proptests at proptest's default 256 cases, each spawning real threads over up
  # to 255 elements. Under Miri's ~1000x slowdown that single suite exceeds a
  # 20-minute budget and is killed mid-run, so an unbounded invocation cannot
  # pass. At 8 cases the full 26-test suite completes cleanly in ~67s. The bound
  # is a scheduling constraint, not a reduction in what is checked: every test
  # still runs, and the shrinker still minimizes any counterexample it finds.
  miri:
    name: Miri (aliasing and data-race detection)
    runs-on: ubuntu-latest
    timeout-minutes: 45
    steps:
      - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
        with:
          persist-credentials: false
      - name: Install the Miri nightly toolchain
        uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # nightly
        with:
          toolchain: nightly
          components: miri
      - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.8.1
        with:
          save-if: ${{ github.ref == 'refs/heads/main' }}
      # Both aliasing models the README names are run, over the same targets.
      # Stacked Borrows is Miri's default; Tree Borrows is a *more permissive*
      # model, so a program clean under Tree Borrows can still be UB under
      # Stacked Borrows. Neither subsumes the other, so both are run in full —
      # a single model would not pin the "Stacked and Tree Borrows" claim.
      - name: Miri — melinoe (Stacked Borrows)
        env:
          PROPTEST_CASES: "8"
          MIRIFLAGS: -Zmiri-disable-isolation
        run: cargo +nightly miri test --locked --all-features

      - name: Miri — melinoe (Tree Borrows)
        env:
          PROPTEST_CASES: "8"
          MIRIFLAGS: -Zmiri-tree-borrows -Zmiri-disable-isolation
        run: cargo +nightly miri test --locked --all-features