macrame-db 0.12.0

A Bitemporal Graph Ledger on libSQL · Embedded knowledge database
Documentation
name: CI

on:
  push:
    branches: [main]
  pull_request:
  workflow_call: # so release.yml can gate on exactly this

env:
  CARGO_TERM_COLOR: always

# Action versions, and why these particular majors (0.8.0, A4, D-112).
#
# `actions/checkout@v4`, `setup-python@v5`, `upload-artifact@v4` and
# `download-artifact@v4` all declare `runs.using: node20` — verified by reading
# each action's own `action.yml`, not inferred from the deprecation banner. Node
# 20 is deprecated and force-migrated to Node 24, which annotated every job of
# every run across all four workflow files.
#
# Each is bumped to the FIRST major that declares `node24`, which is a different
# number for each of them: checkout v5, setup-python v6, upload-artifact v6,
# download-artifact v7. Not to the newest (v7 / v7 / v7 / v8) — that would also
# take an ESM migration, `download-artifact`'s new hash-mismatch enforcement,
# `upload-artifact`'s direct-upload semantics and `checkout`'s fork-PR blocking,
# none of which this repository needs and none of which can be tested anywhere
# but on CI itself. The goal is to stop running a deprecated runtime, and that
# is exactly what these four buy.
#
# `download-artifact` v5's one real breaking change — the output path for
# single-artifact downloads **by ID** — does not apply here: `wheels.yml` names
# no artifact and uses `merge-multiple: true` across all of them.
#
# Not bumped, because they are already fine: `Swatinem/rust-cache@v2` and
# `PyO3/maturin-action@v1` both declare `node24` on their current major, and
# `pypa/gh-action-pypi-publish@release/v1` is a composite action with no Node
# runtime at all. `dtolnay/rust-toolchain` is a branch ref by design.

jobs:
  lint:
    name: clippy
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
      - uses: dtolnay/rust-toolchain@stable
        with:
          components: rustfmt, clippy
      - uses: Swatinem/rust-cache@v2

      # `--all-targets` covers benches and examples, which is where several of
      # this project's diagnostics live and where clippy has caught real defects
      # (D-075's oversized DbError came out of a clippy warning). Verified clean
      # under `-D warnings` at 0.6.0, so this is a gate that currently passes.
      #
      # `-D warnings` is scoped to this step rather than set for the workflow:
      # as a global RUSTFLAGS it also applies to the `cargo publish --dry-run`
      # verification build, where a warning from a dependency's build would fail
      # a packaging check that has nothing to do with warnings.
      #
      # **`property-tests` is in the feature list, and was not until 0.8.0.**
      # `--all-targets` only reaches targets that the selected features compile,
      # so the three quarantined generated-history binaries — which are gated
      # behind `property-tests` — were never linted by this gate at all. That
      # was found by accident in 0.8.0 (D-124), and what it was hiding was an
      # unused import in `graph_property_tests` that had survived every green
      # run. Linting a binary costs nothing here whether or not it is *run*
      # under a feature gate, so the two lists have no reason to differ.
      - name: clippy
        run: cargo clippy --all-targets --features "metrics property-tests"
        env:
          RUSTFLAGS: -D warnings

      # Rustdoc has its own warning set that `cargo clippy` never sees —
      # unresolved intra-doc links, public docs linking to private items,
      # unclosed HTML tags — and nothing in this workflow built the docs, so
      # they accumulated silently: 18 of them by 0.8.0, every one pre-existing
      # and none noticed at the commit that introduced it. A broken
      # `[`Foo::bar`]` renders as literal brackets on docs.rs, which is a
      # published defect in the crate's own documentation.
      #
      # `RUSTDOCFLAGS` rather than `RUSTFLAGS`, and scoped to this step for the
      # same reason clippy's is (see above): as a workflow-level env it would
      # also apply to the `cargo publish --dry-run` job, where a doc warning
      # from a dependency would fail a packaging check for something the
      # release does not control.
      #
      # `--no-deps` so only this crate's docs are built and only this crate's
      # warnings can fail the gate. `--features metrics` matches what the test
      # job documents; `property-tests` adds no library items, so unlike clippy
      # there is nothing extra for it to reach here.
      - name: rustdoc (no broken links)
        run: cargo doc --no-deps --features metrics
        env:
          RUSTDOCFLAGS: -D warnings

      # **Report-only, deliberately.** This repo has never been rustfmt-clean —
      # 246 diffs at 0.6.0, essentially all in tests and benches — so a blocking
      # gate would go red on its first run for a pre-existing condition, and the
      # only way to green it is a repo-wide reformat nobody reviewed. Adopting
      # rustfmt is a decision worth taking on purpose: run `cargo fmt --all` in
      # its own commit, then delete `continue-on-error` here.
      - name: rustfmt (advisory)
        run: cargo fmt --all -- --check
        continue-on-error: true

  # The README carries an MSRV badge and `Cargo.toml` a `rust-version`, and
  # both are claims. Nothing else in the build would notice them going stale:
  # the crate is developed on stable, so a newly used std method raises the real
  # floor silently and the declared one stays where it was.
  #
  # `--all-targets` deliberately: tests and benches use `OnceLock` and other
  # items the library does not, and a contributor hitting a build failure the
  # badge said would not happen is the same defect as a wrong badge.
  msrv:
    name: MSRV (rust-version in Cargo.toml)
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
      - id: msrv
        run: echo "v=$(grep -m1 '^rust-version' Cargo.toml | sed 's/.*= *//; s/\"//g')" >> "$GITHUB_OUTPUT"
      - uses: dtolnay/rust-toolchain@master
        with:
          toolchain: ${{ steps.msrv.outputs.v }}
      - uses: Swatinem/rust-cache@v2
      - run: cargo check --all-features --all-targets

  # `README.md` promises "Windows desktop, Linux, or macOS", and until 0.8.0 the
  # only macOS evidence this project held arrived *through pyo3* — `python.yml`
  # added `macos-latest` at P7, so the crate that is the actual product was
  # tested on two of the three platforms it claims. That is a strange shape for
  # a README claim to rest on, and the fix is four characters.
  #
  # It also buys a measurement worth having on its own: **the R15 rate on Apple
  # silicon is unknown.** Every figure in `.cargo/config.toml` and in the R15
  # risk row is from this Windows machine, and the fault has never been observed
  # on a non-Windows runner — which is not the same as knowing it is absent.
  # A1's classifier is what makes that observable rather than merely noisy: a
  # macOS crash will now say `CRASH` and name its target instead of arriving as
  # a smaller green.
  test:
    name: test (${{ matrix.os }})
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
    steps:
      - uses: actions/checkout@v5
      - uses: dtolnay/rust-toolchain@stable
      - uses: Swatinem/rust-cache@v2

      # For `scripts/run_rust_suite.py`. Pinned rather than relying on the
      # runner's default: `python` and `python3` are not the same name on both
      # images, and a gate that only exists on one OS is not a gate.
      - uses: actions/setup-python@v6
        with:
          python-version: "3.12"

      # `.cargo/config.toml` sets RUST_TEST_THREADS = "1" for R15 and applies
      # here automatically. Read that file before changing anything below.
      #
      # R15 is an intermittent libSQL access violation (0xC0000005) triggered by
      # concurrent database opens. It kills the process, so the affected target
      # never prints its `test result:` line while every other target still
      # prints its own — the run comes back with a SMALLER PASS COUNT AND ZERO
      # FAILURES, which reads as green to anything summing passes, and red to
      # cargo's exit code, for a reason that has nothing to do with the change
      # under test.
      #
      # Both steps below used to be `for attempt in 1 2 3`, which counts
      # failures without reading them: three failures produced four lines of log
      # and none of them said whether libSQL died or a property found a real
      # defect. `scripts/run_rust_suite.py` classifies instead — CRASH, FAILED,
      # INCOMPLETE, TEARDOWN, BUILD — and retries only CRASH. A genuine failure
      # is now reported on attempt 1 with the test named. D-107 did this for the
      # Python suite; D-110 brings it back to Rust.
      # The classifier is verified before it is trusted, and this step exists
      # because verifying it by injection was not enough. Both injections were
      # run locally and both were correct; the first CI run then reported
      # `BUILD` on a suite in which all 27 targets passed, because
      # `CARGO_TERM_COLOR: always` (set above, and not set locally) wraps every
      # cargo status line in SGR escapes and the stderr parse matched nothing.
      #
      # Injection proves the classifier reads a real cargo run. It cannot prove
      # it reads a run shaped the way *this workflow* shapes one. The fixtures
      # include the coloured shape, cost no compile, and run first so a broken
      # classifier is reported as a broken classifier rather than as a broken
      # suite.
      - name: suite gate self-test
        shell: bash
        run: python scripts/run_rust_suite.py --self-test

      - name: test (with R15 retry)
        shell: bash
        run: python scripts/run_rust_suite.py --features metrics --attempts 3

      # The generated-history binaries are quarantined behind a feature (see
      # .cargo/config.toml): a property case opens a database of its own, which
      # is exactly R15's trigger, so they fault far more often than the rest.
      # Run as their own step so their noise cannot be mistaken for the suite's.
      #
      # Six attempts here, not three. The old budget was calibrated on the main
      # suite — "R15 has always passed on re-run" is true where
      # RUST_TEST_THREADS = "1" helps, and these targets are quarantined
      # precisely because it does not: doctrine_property_tests alone measured
      # 9 crashes in 15 runs at 0.7.0. Against p≈0.6 per attempt, three attempts
      # go red about 22% of the time and six about 5% — which matches the 2-in-9
      # observed on `main` in runs 30706318073 and 30706474231, both doc-only
      # commits.
      #
      # p≈0.6 was one binary, one session, n=15. Measured on this step as a
      # whole at 0.12.0 — 93% per attempt, n=100, on a developer Windows box
      # under sustained load (.cargo/config.toml, D-147). Six attempts at that
      # rate is 65%, and this step plainly does not go red 65% of the time
      # here, so the runner is NOT that machine and the two figures bracket it
      # rather than contradict each other: the 2-in-9 observed above implies
      # p≈0.78 on the runner, between 0.6 and 0.93.
      #
      # The honest summary is that this rate is a property of the machine and
      # the load, not a constant of the crate, and no measurement taken
      # anywhere else predicts the runner. The budget is deliberately NOT
      # raised: at any of these rates no budget makes the step a gate, and more
      # attempts only buy more chances to launder a real failure. What the step
      # is for is running these binaries at all and reporting honestly when the
      # engine wins.
      #
      # Raising the budget was rejected while the loop existed, because more
      # attempts also meant more chances to launder a real failure into a pass.
      # The classifier removes that: a FAILED result returns on attempt 1 and is
      # never retried, so the only thing six attempts buys is more tolerance for
      # the one outcome that is known to be noise.
      - name: property tests (quarantined, R15-prone)
        shell: bash
        run: python scripts/run_rust_suite.py --features property-tests --attempts 6

  # A `cargo publish` failure after a tag is pushed is expensive: the tag is
  # already public and the version number is spent. This runs the same
  # packaging and verification step on every PR, so the release job's only new
  # variable is the upload itself.
  package:
    name: cargo publish --dry-run
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
      - uses: dtolnay/rust-toolchain@stable
      - uses: Swatinem/rust-cache@v2
      - run: cargo publish --dry-run