diff --git a/.github/dependabot.yml b/.github/dependabot.yml index d8df49e..8f47b33 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,8 +1,8 @@ # Every `uses:` in the workflows names a commit hash (CLAUDE.md § Standing decisions), and a hash # does not move: dependabot opens a PR per upstream release that rewrites the hash and the version # comment together, and scripts/check-actions-pinned.sh holds that PR to the hash-only form like -# any other. Nothing merges on its own. The tarball digests in golden.yml are not actions and stay -# by hand. +# any other. Nothing merges on its own. The tarball digests in ci.yml's golden jobs are not actions +# and stay by hand. version: 2 updates: - package-ecosystem: github-actions diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d993989..b4af0ad 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,26 +1,93 @@ -# The functional gate, in CI. The gate job runs the same scripts the git hooks run, so local and -# remote green have one definition, less the task store's lint, which smoke.sh skips under CI -# because no job installs meshwork. The other jobs cover what a local run may skip: the MSRV build -# on exactly the default features, cargo-deny, and on the one toolchain dbsp builds on, the dbsp -# feature and the compile-fail wording. Benchmarks and the golden hash have their own workflows. +# The test suite: every check the project makes of its code, in one workflow, at two points. +# +# nightly on dev the default branch, and only when a commit since the last nightly reached +# anything outside docs/meshwork/ (owner: "testing to occur nightly, ONLY when +# there have been code changes since the last run") +# the pull request into main, the dev→main merge that cuts a release (owner: "then, yes, again +# on the PR") +# a dispatch the whole suite on the ref named, on demand +# +# A push runs nothing (owner: "A PUSH IS NOT SUPPOSED TO BE AN INTENSIVE TEST!"): the push gate, +# scripts/regression.sh, ran on the developer's machine. A pull request into dev, dependabot's or the +# release PR, runs nothing either; its merge is a push to dev and the next nightly tests it. +# +# The gate job runs the two scripts the git hooks run, so local and remote green have one definition, +# less the task store's lint, which smoke.sh skips under CI because no job installs meshwork. The other +# jobs cover what a local run may skip: the MSRV build on exactly the default features, cargo-deny, +# the dbsp feature and the compile-fail wording on the one toolchain dbsp builds on, the suite under +# miri, the golden hash on seven targets, and ten minutes of each fuzz target. Benchmarks never run +# here: a speed target is measured into BENCH.md, never gated. name: ci on: - push: - branches: [main, dev] + schedule: + - cron: "17 3 * * *" # 03:17 UTC, on the default branch pull_request: + branches: [main] workflow_dispatch: concurrency: group: ci-${{ github.ref }} cancel-in-progress: true +# Read-only everywhere; the one job that reads the run list grants itself that alone. +permissions: + contents: read + env: CARGO_TERM_COLOR: always jobs: + # Nightly, the question is whether dev moved since the last nightly in anything the suite reads. The + # last completed scheduled run on this branch that ended in success or failure names the commit it + # ran at: a night that skipped ends in success at the commit it declined, so the chain never loses a + # link, and a cancelled run tested nothing and is passed over. A diff from there to this commit that + # stays inside docs/meshwork/, the task store, which smoke.sh skips under CI, is no code change, and + # every other job is skipped. A pull request or a dispatch always runs. gh and git are the runner's + # own: no action, no marker cache to evict. + changes: + name: code changed since the last nightly + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + contents: read + actions: read # gh run list + outputs: + run: ${{ steps.since.outputs.run }} + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - id: since + env: + GH_TOKEN: ${{ github.token }} + run: | + if [ "$GITHUB_EVENT_NAME" != "schedule" ]; then + echo "run=true" >> "$GITHUB_OUTPUT" + echo "ci: a $GITHUB_EVENT_NAME runs the suite" + exit 0 + fi + last=$(gh run list --workflow ci.yml --event schedule --branch "$GITHUB_REF_NAME" --status completed --limit 20 --json headSha,conclusion \ + --jq '[.[] | select(.conclusion == "success" or .conclusion == "failure")][0].headSha // empty') + if [ -z "$last" ]; then + echo "run=true" >> "$GITHUB_OUTPUT" + echo "ci: no earlier nightly on $GITHUB_REF_NAME; the suite runs" + exit 0 + fi + if ! git fetch -q --depth=1 origin "$last"; then + echo "run=true" >> "$GITHUB_OUTPUT" + echo "ci: the last nightly's commit $last is not reachable from $GITHUB_REF_NAME; the suite runs" + exit 0 + fi + if git diff --quiet "$last" "$GITHUB_SHA" -- . ':!docs/meshwork'; then + echo "run=false" >> "$GITHUB_OUTPUT" + echo "ci: nothing outside docs/meshwork/ changed since the last nightly at ${last:0:12}; the suite is skipped" + else + echo "run=true" >> "$GITHUB_OUTPUT" + echo "ci: changed since the last nightly at ${last:0:12}:" + git --no-pager diff --stat "$last" "$GITHUB_SHA" -- . ':!docs/meshwork' + fi + # The version is derived from the log (DESIGN.md § Release), so every commit a PR brings and the - # PR's own title have to be Conventional Commits release-plz can read. The same script is the + # PR's own title have to be Conventional Commits the version count can read. The same script is the # commit-msg hook; the shas and the title reach the shell through env, never by interpolation. commits: name: Conventional Commits on the PR @@ -28,7 +95,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 5 steps: - - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 - name: Every commit and the title @@ -42,16 +109,18 @@ jobs: gate: name: smoke + regression (stable) + needs: changes + if: needs.changes.outputs.run == 'true' runs-on: ubuntu-latest timeout-minutes: 30 steps: - - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Install the toolchain run: | rustup toolchain install stable --profile minimal --component rustfmt,clippy rustup default stable cargo --version && rustc --version - - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: | ~/.cargo/registry/index @@ -64,10 +133,12 @@ jobs: msrv: name: MSRV 1.74 on the default features + needs: changes + if: needs.changes.outputs.run == 'true' runs-on: ubuntu-latest timeout-minutes: 15 steps: - - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Install rustc 1.74.0 run: | rustup toolchain install 1.74.0 --profile minimal @@ -82,10 +153,12 @@ jobs: # crate that earned it. deny: name: cargo-deny + needs: changes + if: needs.changes.outputs.run == 'true' runs-on: ubuntu-latest timeout-minutes: 10 steps: - - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: EmbarkStudios/cargo-deny-action@3c6349835b2b7b196a839186cb8b78e02f7b5f25 # v2.1.1 with: command: check @@ -101,10 +174,12 @@ jobs: # gate job, on a moving stable without rust-src, only runs the pass cases. dbsp: name: dbsp feature and compile-fail wording on rustc 1.99.0 + needs: changes + if: needs.changes.outputs.run == 'true' runs-on: ubuntu-latest timeout-minutes: 45 steps: - - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 # The dbsp closure is some five hundred crates, and the hosted runner's root disk shares its # headroom with the image's preinstalled tools, so the trees this job never reads go first: # the Android SDK, the .NET SDKs, GHCup's Haskell and the CodeQL bundle, at the paths the @@ -122,7 +197,7 @@ jobs: cargo --version && rustc --version # No restore-keys: a lockfile change builds from a clean target, not on top of the previous # closure's artifacts, which a restored near-miss would leave on the disk beside the new ones. - - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: | ~/.cargo/registry/index @@ -153,17 +228,19 @@ jobs: # through `Exact::from_bytes`, which the suite covers. miri: name: cargo miri test (nightly) + needs: changes + if: needs.changes.outputs.run == 'true' runs-on: ubuntu-latest timeout-minutes: 60 steps: - - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Install nightly with miri run: | rustup toolchain install nightly --profile minimal --component miri,rust-src rustup default nightly cargo miri setup cargo --version && rustc --version && cargo miri --version - - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: | ~/.cargo/registry/index @@ -174,3 +251,161 @@ jobs: - run: cargo miri test --locked --lib - run: cargo miri test --locked -p float-abel-noalloc - run: cargo miri test --locked --test main + + # The architecture claim, DESIGN.md § Bit-identical across architectures: tests/golden/ hashes every + # tier's bytes and reads on fixed inputs, and the hash must equal tests/data/golden.sha256 on all + # seven targets. The committed hash is the claim: a mismatch on any target is a finding to run + # down, never a reason to re-bless. The macOS leg runs whenever the suite does, whatever it bills, + # because the committed hash is blessed on that platform (CLAUDE.md § Standing decisions). + golden: + name: golden ${{ matrix.name }} + needs: changes + if: needs.changes.outputs.run == 'true' + runs-on: ${{ matrix.os }} + timeout-minutes: 20 + strategy: + fail-fast: false + matrix: + include: + - { name: x86_64 Linux, os: ubuntu-latest } + - { name: x86_64 Windows, os: windows-latest } + - { name: aarch64 macOS, os: macos-latest } + - { name: aarch64 Linux, os: ubuntu-24.04-arm } + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - name: Install the toolchain + run: | + rustup toolchain install stable --profile minimal + rustup default stable + cargo --version && rustc --version + - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: | + ~/.cargo/registry/index + ~/.cargo/registry/cache + target + key: golden-${{ matrix.os }}-${{ hashFiles('Cargo.lock') }} + restore-keys: golden-${{ matrix.os }}- + - run: cargo test --locked -p float-abel-golden + + # 32-bit pointers, no hardware u128, a software SHA-256: the test binary runs under wasmtime. + golden-wasm32: + name: golden wasm32 (wasmtime) + needs: changes + if: needs.changes.outputs.run == 'true' + runs-on: ubuntu-latest + timeout-minutes: 20 + env: + WASMTIME_VERSION: v49.0.2 + # sha256 of wasmtime-${WASMTIME_VERSION}-x86_64-linux.tar.xz: the release API's asset digest + # (`gh api repos/bytecodealliance/wasmtime/releases/tags/${WASMTIME_VERSION}`). Bump both. + WASMTIME_SHA256: a4d6e9e3a5a60f527cf7793d674c48930c80c2e8977995b8a275cad3254b9322 + CARGO_TARGET_WASM32_WASIP1_RUNNER: wasmtime + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - name: Install the toolchain and the target + run: | + rustup toolchain install stable --profile minimal + rustup default stable + rustup target add wasm32-wasip1 + cargo --version && rustc --version + # Download to a file and check its digest before anything unpacks: a replaced release asset + # fails here instead of running on the runner. + - name: Install wasmtime + run: | + tarball="${RUNNER_TEMP}/wasmtime-${WASMTIME_VERSION}-x86_64-linux.tar.xz" + curl -sSfL -o "$tarball" "https://github.com/bytecodealliance/wasmtime/releases/download/${WASMTIME_VERSION}/wasmtime-${WASMTIME_VERSION}-x86_64-linux.tar.xz" + echo "${WASMTIME_SHA256} ${tarball}" | sha256sum -c - + tar xJf "$tarball" -C "$RUNNER_TEMP" + echo "${RUNNER_TEMP}/wasmtime-${WASMTIME_VERSION}-x86_64-linux" >> "$GITHUB_PATH" + - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: | + ~/.cargo/registry/index + ~/.cargo/registry/cache + target + key: golden-wasm32-${{ hashFiles('Cargo.lock') }} + restore-keys: golden-wasm32- + - run: wasmtime --version + - run: cargo test --locked -p float-abel-golden --target wasm32-wasip1 + + # riscv64gc and big-endian s390x under QEMU: cross builds in its pinned Docker image and runs the + # test binary through the image's qemu-user. s390x is what catches a byte-order slip. + golden-emulated: + name: golden ${{ matrix.target }} (QEMU via cross) + needs: changes + if: needs.changes.outputs.run == 'true' + runs-on: ubuntu-latest + timeout-minutes: 45 + strategy: + fail-fast: false + matrix: + target: [riscv64gc-unknown-linux-gnu, s390x-unknown-linux-gnu] + env: + CROSS_VERSION: v0.2.5 + # sha256 of cross-x86_64-unknown-linux-gnu.tar.gz, computed from the download: the release + # predates the API's asset digest. The binary inside reports `cross 0.2.5`. Bump both. + CROSS_SHA256: 642375d1bcf3bd88272c32ba90e999f3d983050adf45e66bd2d3887e8e838bad + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - name: Install the toolchain and the target + run: | + rustup toolchain install stable --profile minimal + rustup default stable + rustup target add ${{ matrix.target }} + cargo --version && rustc --version + # Download to a file and check its digest before anything unpacks, as the wasm32 job does. + - name: Install cross + run: | + tarball="${RUNNER_TEMP}/cross-${CROSS_VERSION}-x86_64-unknown-linux-gnu.tar.gz" + curl -sSfL -o "$tarball" "https://github.com/cross-rs/cross/releases/download/${CROSS_VERSION}/cross-x86_64-unknown-linux-gnu.tar.gz" + echo "${CROSS_SHA256} ${tarball}" | sha256sum -c - + tar xzf "$tarball" -C ~/.cargo/bin + cross --version + - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: | + ~/.cargo/registry/index + ~/.cargo/registry/cache + target + key: golden-${{ matrix.target }}-${{ hashFiles('Cargo.lock') }} + restore-keys: golden-${{ matrix.target }}- + - run: cross test --locked -p float-abel-golden --target ${{ matrix.target }} + + # The fuzz targets, DESIGN.md § Hardening: cargo-fuzz runs each for ten minutes. A crash is an + # artifact to download and commit as a regression test under tests/, never a reason to loosen the + # target. + fuzz: + name: fuzz ${{ matrix.target }} (10 min) + needs: changes + if: needs.changes.outputs.run == 'true' + runs-on: ubuntu-latest + timeout-minutes: 40 + strategy: + fail-fast: false + matrix: + target: [decode_robustness, sum_vs_oracle, sub_roundtrip] + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - name: Install nightly and cargo-fuzz + run: | + rustup toolchain install nightly --profile minimal --component rust-src + rustup default nightly + cargo install cargo-fuzz@0.13.2 --locked + cargo --version && rustc --version && cargo fuzz --version + - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: | + ~/.cargo/registry/index + ~/.cargo/registry/cache + fuzz/target + key: fuzz-${{ hashFiles('fuzz/Cargo.lock') }} + restore-keys: fuzz- + - run: cargo fuzz run ${{ matrix.target }} -- -max_total_time=600 + - name: Keep the crash + if: failure() + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 + with: + name: ${{ matrix.target }}-artifacts + path: fuzz/artifacts + if-no-files-found: ignore diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml deleted file mode 100644 index a3e8264..0000000 --- a/.github/workflows/fuzz.yml +++ /dev/null @@ -1,57 +0,0 @@ -# The fuzz targets, DESIGN.md § Hardening: cargo-fuzz runs each for ten minutes nightly and on -# demand, and on every push the targets build, so none can rot between nights. A crash is an -# artifact to download and commit as a regression test under tests/, never a reason to loosen the -# target. Its own workflow, apart from ci.yml, because a nightly run is not a gate on a push. -name: fuzz - -on: - push: - branches: [main] - pull_request: - schedule: - - cron: "17 3 * * *" - workflow_dispatch: - -concurrency: - group: fuzz-${{ github.ref }} - cancel-in-progress: true - -env: - CARGO_TERM_COLOR: always - -jobs: - fuzz: - name: ${{ matrix.target }} - runs-on: ubuntu-latest - timeout-minutes: 40 - strategy: - fail-fast: false - matrix: - target: [decode_robustness, sum_vs_oracle, sub_roundtrip] - steps: - - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 - - name: Install nightly and cargo-fuzz - run: | - rustup toolchain install nightly --profile minimal --component rust-src - rustup default nightly - cargo install cargo-fuzz@0.13.2 --locked - cargo --version && rustc --version && cargo fuzz --version - - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 - with: - path: | - ~/.cargo/registry/index - ~/.cargo/registry/cache - fuzz/target - key: fuzz-${{ hashFiles('fuzz/Cargo.lock') }} - restore-keys: fuzz- - - run: cargo fuzz build ${{ matrix.target }} - - name: Run for ten minutes - if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' - run: cargo fuzz run ${{ matrix.target }} -- -max_total_time=600 - - name: Keep the crash - if: failure() - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 - with: - name: ${{ matrix.target }}-artifacts - path: fuzz/artifacts - if-no-files-found: ignore diff --git a/.github/workflows/golden.yml b/.github/workflows/golden.yml deleted file mode 100644 index b4e4df1..0000000 --- a/.github/workflows/golden.yml +++ /dev/null @@ -1,129 +0,0 @@ -# The architecture claim, DESIGN.md § Bit-identical across architectures: tests/golden/ hashes every -# tier's bytes and reads on fixed inputs, and the hash must equal tests/data/golden.sha256 on all -# seven targets. Its own workflow, apart from ci.yml, so that one runner's or emulator's outage never -# blocks the functional gate. The committed hash is the claim: a mismatch on any target is a finding -# to run down, never a reason to re-bless. -name: golden - -on: - push: - branches: [main] - pull_request: - workflow_dispatch: - -concurrency: - group: golden-${{ github.ref }} - cancel-in-progress: true - -env: - CARGO_TERM_COLOR: always - -jobs: - native: - name: ${{ matrix.name }} - runs-on: ${{ matrix.os }} - timeout-minutes: 20 - strategy: - fail-fast: false - matrix: - include: - - { name: x86_64 Linux, os: ubuntu-latest } - - { name: x86_64 Windows, os: windows-latest } - - { name: aarch64 macOS, os: macos-latest } - - { name: aarch64 Linux, os: ubuntu-24.04-arm } - steps: - - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 - - name: Install the toolchain - run: | - rustup toolchain install stable --profile minimal - rustup default stable - cargo --version && rustc --version - - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 - with: - path: | - ~/.cargo/registry/index - ~/.cargo/registry/cache - target - key: golden-${{ matrix.os }}-${{ hashFiles('Cargo.lock') }} - restore-keys: golden-${{ matrix.os }}- - - run: cargo test --locked -p float-abel-golden - - # 32-bit pointers, no hardware u128, a software SHA-256: the test binary runs under wasmtime. - wasm32: - name: wasm32 (wasmtime) - runs-on: ubuntu-latest - timeout-minutes: 20 - env: - WASMTIME_VERSION: v49.0.2 - # sha256 of wasmtime-${WASMTIME_VERSION}-x86_64-linux.tar.xz: the release API's asset digest - # (`gh api repos/bytecodealliance/wasmtime/releases/tags/${WASMTIME_VERSION}`). Bump both. - WASMTIME_SHA256: a4d6e9e3a5a60f527cf7793d674c48930c80c2e8977995b8a275cad3254b9322 - CARGO_TARGET_WASM32_WASIP1_RUNNER: wasmtime - steps: - - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 - - name: Install the toolchain and the target - run: | - rustup toolchain install stable --profile minimal - rustup default stable - rustup target add wasm32-wasip1 - cargo --version && rustc --version - # Download to a file and check its digest before anything unpacks: a replaced release asset - # fails here instead of running on the runner. - - name: Install wasmtime - run: | - tarball="${RUNNER_TEMP}/wasmtime-${WASMTIME_VERSION}-x86_64-linux.tar.xz" - curl -sSfL -o "$tarball" "https://github.com/bytecodealliance/wasmtime/releases/download/${WASMTIME_VERSION}/wasmtime-${WASMTIME_VERSION}-x86_64-linux.tar.xz" - echo "${WASMTIME_SHA256} ${tarball}" | sha256sum -c - - tar xJf "$tarball" -C "$RUNNER_TEMP" - echo "${RUNNER_TEMP}/wasmtime-${WASMTIME_VERSION}-x86_64-linux" >> "$GITHUB_PATH" - - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 - with: - path: | - ~/.cargo/registry/index - ~/.cargo/registry/cache - target - key: golden-wasm32-${{ hashFiles('Cargo.lock') }} - restore-keys: golden-wasm32- - - run: wasmtime --version - - run: cargo test --locked -p float-abel-golden --target wasm32-wasip1 - - # riscv64gc and big-endian s390x under QEMU: cross builds in its pinned Docker image and runs the - # test binary through the image's qemu-user. s390x is what catches a byte-order slip. - emulated: - name: ${{ matrix.target }} (QEMU via cross) - runs-on: ubuntu-latest - timeout-minutes: 45 - strategy: - fail-fast: false - matrix: - target: [riscv64gc-unknown-linux-gnu, s390x-unknown-linux-gnu] - env: - CROSS_VERSION: v0.2.5 - # sha256 of cross-x86_64-unknown-linux-gnu.tar.gz, computed from the download: the release - # predates the API's asset digest. The binary inside reports `cross 0.2.5`. Bump both. - CROSS_SHA256: 642375d1bcf3bd88272c32ba90e999f3d983050adf45e66bd2d3887e8e838bad - steps: - - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 - - name: Install the toolchain and the target - run: | - rustup toolchain install stable --profile minimal - rustup default stable - rustup target add ${{ matrix.target }} - cargo --version && rustc --version - # Download to a file and check its digest before anything unpacks, as the wasm32 job does. - - name: Install cross - run: | - tarball="${RUNNER_TEMP}/cross-${CROSS_VERSION}-x86_64-unknown-linux-gnu.tar.gz" - curl -sSfL -o "$tarball" "https://github.com/cross-rs/cross/releases/download/${CROSS_VERSION}/cross-x86_64-unknown-linux-gnu.tar.gz" - echo "${CROSS_SHA256} ${tarball}" | sha256sum -c - - tar xzf "$tarball" -C ~/.cargo/bin - cross --version - - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 - with: - path: | - ~/.cargo/registry/index - ~/.cargo/registry/cache - target - key: golden-${{ matrix.target }}-${{ hashFiles('Cargo.lock') }} - restore-keys: golden-${{ matrix.target }}- - - run: cross test --locked -p float-abel-golden --target ${{ matrix.target }} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..1a5f630 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,110 @@ +# The crates.io publish, on the owner's manual trigger alone (CLAUDE.md § Standing decisions: "make +# it a ci publish on my manual trigger"): never on a tag push, so a tag on its own publishes nothing. +# +# a dispatch names the tag release.yml cut and the authentication +# +# `token` reads the repository secret CARGO_REGISTRY_TOKEN, the first publish's route, because +# crates.io grants Trusted Publishing only to a crate that already exists; `trusted` exchanges the +# job's OIDC token for a thirty-minute crates.io token through rust-lang/crates-io-auth-action, every +# later release's route once the owner configures it on the crate (owner jbrjake, repository +# float-abel, workflow publish.yml, environment release). The .crate is built with --locked, attested +# with actions/attest-build-provenance, which needs the repository public, and uploaded onto the +# GitHub Release release.yml made for the tag. This workflow is outside the release process until the +# owner brings crates.io in. +name: publish + +on: + workflow_dispatch: + inputs: + tag: + description: "The tag to publish, vX.Y.Z, naming Cargo.toml's version" + required: true + type: string + auth: + description: "trusted: crates.io Trusted Publishing; token: the CARGO_REGISTRY_TOKEN secret, the first publish's route" + required: true + type: choice + options: [trusted, token] + default: trusted + +# One publish at a time, and a running one is never cancelled under a second dispatch. +concurrency: + group: publish + cancel-in-progress: false + +env: + CARGO_TERM_COLOR: always + +permissions: {} + +jobs: + publish: + name: cargo publish ${{ inputs.tag }} by ${{ inputs.auth }} + runs-on: ubuntu-latest + timeout-minutes: 30 + environment: release + permissions: + contents: write # the GitHub release and its asset + id-token: write # the OIDC token, for crates.io's exchange and the attestation's signing + attestations: write # the provenance attestation + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: refs/tags/${{ inputs.tag }} + # The inputs reach the shell through env, never by interpolation into a script. + - name: The tag names Cargo.toml's version + env: + TAG: ${{ inputs.tag }} + run: | + version=$(cargo metadata --no-deps --format-version 1 | jq -r '.packages[] | select(.name == "float-abel") | .version') + if [ "$TAG" != "v$version" ]; then + echo "release: tag $TAG does not name Cargo.toml's version $version" >&2 + exit 1 + fi + echo "release: $TAG is version $version" + - name: Install the toolchain + run: | + rustup toolchain install stable --profile minimal + rustup default stable + cargo --version && rustc --version + - name: Authenticate with crates.io by Trusted Publishing + if: inputs.auth == 'trusted' + id: auth + uses: rust-lang/crates-io-auth-action@3268eb413133e2960fa4ae0e963ca76380c54cd9 # v1.0.6 + # cargo package builds the archive from the tag's tree and lockfile, builds it once more from + # the archive to verify it, and leaves it at target/package/. The attestation is of that file. + - name: Package with the lockfile + run: | + cargo package --locked + sha256sum target/package/float-abel-*.crate | tee target/package/sha256 + - name: Attest the .crate's provenance + uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2 + with: + subject-path: target/package/float-abel-*.crate + # cargo publish re-packages the same tree with the same cargo, byte for byte, and uploads it; + # --no-verify skips the second verify build, not the packaging. A missing token fails before + # anything is uploaded, and the digest is re-asserted on whatever cargo leaves behind. + - name: Publish + env: + AUTH: ${{ inputs.auth }} + TRUSTED_TOKEN: ${{ steps.auth.outputs.token }} + SECRET_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} + run: | + case "$AUTH" in + trusted) token="$TRUSTED_TOKEN" ;; + token) token="$SECRET_TOKEN" ;; + *) token="" ;; + esac + if [ -z "$token" ]; then + echo "release: no crates.io token for auth=$AUTH" >&2 + exit 1 + fi + CARGO_REGISTRY_TOKEN="$token" cargo publish --locked --no-verify + if ls target/package/float-abel-*.crate >/dev/null 2>&1; then + sha256sum --check target/package/sha256 + fi + - name: Upload the published .crate onto the tag's GitHub release + env: + GH_TOKEN: ${{ github.token }} + TAG: ${{ inputs.tag }} + run: gh release upload "$TAG" target/package/float-abel-*.crate --clobber diff --git a/.github/workflows/release-plz.yml b/.github/workflows/release-plz.yml deleted file mode 100644 index e641396..0000000 --- a/.github/workflows/release-plz.yml +++ /dev/null @@ -1,168 +0,0 @@ -# Cuts releases from the conventional-commit log (DESIGN.md § Release). On a push to dev, -# `release-plz release-pr` opens or refreshes the release PR against dev, carrying the version and -# the CHANGELOG entry the log since the last tag yields. On a push to main, the dev→main merge, -# `release-plz release` tags vX.Y.Z and publishes the GitHub Release when Cargo.toml's version has no -# tag yet, and the same job then builds the .crate from that tag with --locked, attests its -# provenance with GitHub artifact attestations (Sigstore, bound to this repository, this workflow -# and the commit) and attaches the .crate, its sha256 and the attestation to the release. git-only: -# the tag says what is released, never a registry, because crates.io is outside the release until -# the owner says (CLAUDE.md § Standing decisions). The binaries are pinned by hash like the actions -# are: release-plz by its tarball's sha256 in this file, cargo-semver-checks through -# taiki-e/install-action, whose manifest pins a sha256 per platform for the version named. -name: release-plz - -on: - push: - branches: [dev, main] - -permissions: {} - -env: - CARGO_TERM_COLOR: always - -jobs: - release-pr: - name: release PR against dev - if: github.ref == 'refs/heads/dev' - runs-on: ubuntu-latest - timeout-minutes: 20 - permissions: - contents: write # the release-plz-* branch - pull-requests: write # the release PR - concurrency: - group: release-plz-pr - cancel-in-progress: false - steps: - - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 - with: - fetch-depth: 0 # every tag and the whole log, which the version is read from - persist-credentials: false - - name: Install the toolchain - run: | - rustup toolchain install stable --profile minimal - rustup default stable - cargo --version && rustc --version - - uses: taiki-e/install-action@1919c4a95903d2c33e4a221102edca87309fc14b # v2.89.0 - with: - tool: cargo-semver-checks@0.51.0 - # release-plz by its tarball's sha256. install-action's manifest stops at 0.4.2, whose musl - # build fails parsing CHANGELOG.md's header ("stack limit reached") where 0.4.2 gnu and both - # 0.4.3 builds pass, so the binary is pinned by hash directly: the hash was taken on two - # machines from the release asset. - - name: Install release-plz 0.4.3 by hash - env: - URL: https://github.com/release-plz/release-plz/releases/download/release-plz-v0.4.3/release-plz-x86_64-unknown-linux-gnu.tar.gz - SHA256: f4b0cc404cd9a58cd1b33f49e439bba65186773f63e323ea47f3ea57e557a3d3 - run: | - curl -sSL --retry 3 -o "$RUNNER_TEMP/release-plz.tar.gz" "$URL" - echo "$SHA256 $RUNNER_TEMP/release-plz.tar.gz" | sha256sum --check - mkdir -p "$RUNNER_TEMP/bin" - tar -xzf "$RUNNER_TEMP/release-plz.tar.gz" -C "$RUNNER_TEMP/bin" release-plz - echo "$RUNNER_TEMP/bin" >> "$GITHUB_PATH" - "$RUNNER_TEMP/bin/release-plz" --version - # The release PR's base is the repository's default branch, which release-plz offers no flag - # for, so a dev whose repository still defaults to main would get a release PR against main. - - name: The default branch is dev - env: - GH_TOKEN: ${{ github.token }} - run: | - default=$(gh api "repos/$GITHUB_REPOSITORY" --jq '.default_branch') - if [ "$default" != "dev" ]; then - echo "release-plz: the default branch is $default, not dev; the release PR would target it. Set dev as the default branch (Settings → General) and push again." >&2 - exit 1 - fi - # release-plz commits through the GraphQL API, so the commit is GitHub-signed; the identity - # below is for the temporary local repository it diffs in. A PR opened with GITHUB_TOKEN runs - # no workflows, so the release PR gets ci once the owner stores a GitHub App or fine-grained - # PAT token as RELEASE_PLZ_TOKEN; until then the PR still opens. - - name: release-plz release-pr - env: - GITHUB_TOKEN: ${{ secrets.RELEASE_PLZ_TOKEN || github.token }} - run: | - set -o pipefail # release-plz's exit status, not tee's - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - # The JSON lands outside the tree: release-plz refuses a working directory with an untracked file. - release-plz release-pr --git-token "$GITHUB_TOKEN" --repo-url "$GITHUB_SERVER_URL/$GITHUB_REPOSITORY" -o json | tee "$RUNNER_TEMP/release-pr.json" - jq -e '.prs' "$RUNNER_TEMP/release-pr.json" >/dev/null - - release: - name: release on main - if: github.ref == 'refs/heads/main' - runs-on: ubuntu-latest - timeout-minutes: 30 - permissions: - contents: write # the tag, the GitHub Release and its assets - pull-requests: read # release-plz reads the PRs the released commit came from, for the notes - id-token: write # the attestation's Sigstore signing - attestations: write # the provenance attestation - concurrency: - group: release-plz-release - cancel-in-progress: false - steps: - - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 - with: - fetch-depth: 0 - persist-credentials: false - - name: Install the toolchain - run: | - rustup toolchain install stable --profile minimal - rustup default stable - cargo --version && rustc --version - - name: Install release-plz 0.4.3 by hash - env: - URL: https://github.com/release-plz/release-plz/releases/download/release-plz-v0.4.3/release-plz-x86_64-unknown-linux-gnu.tar.gz - SHA256: f4b0cc404cd9a58cd1b33f49e439bba65186773f63e323ea47f3ea57e557a3d3 - run: | - curl -sSL --retry 3 -o "$RUNNER_TEMP/release-plz.tar.gz" "$URL" - echo "$SHA256 $RUNNER_TEMP/release-plz.tar.gz" | sha256sum --check - mkdir -p "$RUNNER_TEMP/bin" - tar -xzf "$RUNNER_TEMP/release-plz.tar.gz" -C "$RUNNER_TEMP/bin" release-plz - echo "$RUNNER_TEMP/bin" >> "$GITHUB_PATH" - "$RUNNER_TEMP/bin/release-plz" --version - # Tags vX.Y.Z and publishes the GitHub Release when the version has no tag; an empty - # `releases` means main moved without a version change and the steps below are skipped. - - name: release-plz release - id: release - env: - GITHUB_TOKEN: ${{ secrets.RELEASE_PLZ_TOKEN || github.token }} - run: | - set -o pipefail # release-plz's exit status, not tee's - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - release-plz release --git-token "$GITHUB_TOKEN" -o json | tee "$RUNNER_TEMP/release.json" - tag=$(jq -r '.releases[0].tag // empty' "$RUNNER_TEMP/release.json") - echo "tag=$tag" >> "$GITHUB_OUTPUT" - # The tag was made through the API, so the local clone learns its commit from the API too. - # The commit is already here: release-plz tags the merged release PR's last commit or main's - # head, both in the clone's history. - - name: Package the .crate from the tag - if: steps.release.outputs.tag != '' - env: - GH_TOKEN: ${{ github.token }} - TAG: ${{ steps.release.outputs.tag }} - run: | - sha=$(gh api "repos/$GITHUB_REPOSITORY/git/ref/tags/$TAG" --jq '.object.sha') - type=$(gh api "repos/$GITHUB_REPOSITORY/git/ref/tags/$TAG" --jq '.object.type') - if [ "$type" = "tag" ]; then - sha=$(gh api "repos/$GITHUB_REPOSITORY/git/tags/$sha" --jq '.object.sha') - fi - git checkout --quiet "$sha" - version=$(cargo metadata --no-deps --format-version 1 | jq -r '.packages[] | select(.name == "float-abel") | .version') - if [ "$TAG" != "v$version" ]; then - echo "release-plz: tag $TAG does not name Cargo.toml's version $version at $sha" >&2 - exit 1 - fi - cargo package --locked - (cd target/package && sha256sum float-abel-"$version".crate | tee float-abel-"$version".crate.sha256) - - name: Attest the .crate's provenance - if: steps.release.outputs.tag != '' - uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2 - with: - subject-path: target/package/float-abel-*.crate - - name: Attach the .crate and its sha256 to the release - if: steps.release.outputs.tag != '' - env: - GH_TOKEN: ${{ github.token }} - TAG: ${{ steps.release.outputs.tag }} - run: gh release upload "$TAG" target/package/float-abel-*.crate target/package/float-abel-*.crate.sha256 --clobber diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b79c583..59f439b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,108 +1,105 @@ -# Publishes float-abel to crates.io on the owner's manual trigger alone (CLAUDE.md § Standing -# decisions: "make it a ci publish on my manual trigger"): never on a tag push, so a tag on its own -# publishes nothing. The dispatch names the tag and the authentication. `token` reads the repository -# secret CARGO_REGISTRY_TOKEN, the first publish's route, because crates.io grants Trusted Publishing -# only to a crate that already exists; `trusted` exchanges the job's OIDC token for a thirty-minute -# crates.io token through rust-lang/crates-io-auth-action, every later release's route once the -# owner configures it on the crate (owner jbrjake, repository float-abel, workflow release.yml, -# environment release). The .crate is built with --locked, attested with -# actions/attest-build-provenance, which needs the repository public, and uploaded onto the GitHub -# release release-plz made for the tag (release-plz.yml). This workflow is outside the release -# process until the owner brings crates.io in. +# The release, cut from the conventional-commit log counted from 0.0.0 over every commit since the +# first (DESIGN.md § Release; scripts/version-from-log.sh). The release is one pull request, dev into +# main, and it is not this workflow's: scripts/release-pr.sh commits the version onto dev and opens +# the pull request from a clone under the developer's own gh login, because a commit and a pull +# request carry no identity a runner could add, and no runner holds write access to the repository +# for them. The attestation does carry the runner's identity, so that step is here. +# +# the push to main the dev→main merge: refuses a Cargo.toml that disagrees with the count and, +# when the version has no tag yet, builds the .crate from that commit with +# --locked, attests its provenance with GitHub artifact attestations, then +# creates the tag and the GitHub Release with the entry as its notes and the +# .crate, its sha256 and the attestation attached +# +# A push to dev is just a push and starts nothing here; ci.yml tests dev nightly and on the pull +# request into main. Nothing runs here but git, cargo and gh on the runner, the repository's own +# scripts, and the two GitHub-owned actions already pinned in this repository: the project's posture +# is no dependency it does not need, in the build as in the crate. crates.io is outside the release +# until the owner says (CLAUDE.md § Standing decisions); publish.yml is that step, on its own dispatch. name: release on: - workflow_dispatch: - inputs: - tag: - description: "The tag to publish, vX.Y.Z, naming Cargo.toml's version" - required: true - type: string - auth: - description: "trusted: crates.io Trusted Publishing; token: the CARGO_REGISTRY_TOKEN secret, the first publish's route" - required: true - type: choice - options: [trusted, token] - default: trusted + push: + branches: [main] -# One release at a time, and a running one is never cancelled under a second dispatch. -concurrency: - group: release - cancel-in-progress: false +permissions: {} env: CARGO_TERM_COLOR: always -permissions: {} - jobs: - publish: - name: cargo publish ${{ inputs.tag }} by ${{ inputs.auth }} + release: + name: release on main runs-on: ubuntu-latest timeout-minutes: 30 - environment: release permissions: - contents: write # the GitHub release and its asset - id-token: write # the OIDC token, for crates.io's exchange and the attestation's signing + contents: write # the tag, the GitHub Release and its assets + id-token: write # the attestation's Sigstore signing attestations: write # the provenance attestation + concurrency: + group: release + cancel-in-progress: false steps: - - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: - ref: refs/tags/${{ inputs.tag }} - # The inputs reach the shell through env, never by interpolation into a script. - - name: The tag names Cargo.toml's version - env: - TAG: ${{ inputs.tag }} - run: | - version=$(cargo metadata --no-deps --format-version 1 | jq -r '.packages[] | select(.name == "float-abel") | .version') - if [ "$TAG" != "v$version" ]; then - echo "release: tag $TAG does not name Cargo.toml's version $version" >&2 - exit 1 - fi - echo "release: $TAG is version $version" + fetch-depth: 0 + persist-credentials: false - name: Install the toolchain run: | rustup toolchain install stable --profile minimal rustup default stable cargo --version && rustc --version - - name: Authenticate with crates.io by Trusted Publishing - if: inputs.auth == 'trusted' - id: auth - uses: rust-lang/crates-io-auth-action@c6f97d42243bad5fab37ca0427f495c86d5b1a18 # v1.0.5 - # cargo package builds the archive from the tag's tree and lockfile, builds it once more from - # the archive to verify it, and leaves it at target/package/. The attestation is of that file. - - name: Package with the lockfile + # A main whose Cargo.toml disagrees with the count had dev merged before scripts/release-pr.sh + # ran on it; the job stops there rather than tag the wrong number. A version with its tag is + # already released. + - name: The version Cargo.toml carries is the one the log counts to + id: version + run: | + counted=$(./scripts/version-from-log.sh) + carried=$(awk -F'"' '/^version = "/ { print $2; exit }' Cargo.toml) + if [ "$counted" != "$carried" ]; then + echo "release: Cargo.toml says $carried but the log counts to $counted; run scripts/release-pr.sh on dev before merging dev into main" >&2 + exit 1 + fi + echo "version=$counted" >> "$GITHUB_OUTPUT" + if git rev-parse -q --verify "refs/tags/v$counted" >/dev/null; then + echo "release: v$counted is already released" + echo "tag=" >> "$GITHUB_OUTPUT" + else + echo "tag=v$counted" >> "$GITHUB_OUTPUT" + fi + # The entry is read before anything is built or published, so a CHANGELOG.md without one stops + # the job with no tag made. + - name: Read the entry for the release notes + if: steps.version.outputs.tag != '' + env: + VERSION: ${{ steps.version.outputs.version }} + run: | + awk -v v="$VERSION" 'index($0, "## [" v "]") == 1 { p = 1; print; next } /^## \[/ { p = 0 } p { print }' CHANGELOG.md > "$RUNNER_TEMP/notes.md" + if [ ! -s "$RUNNER_TEMP/notes.md" ]; then + echo "release: CHANGELOG.md carries no entry for $VERSION" >&2 + exit 1 + fi + # cargo package builds the archive from this commit's tree and lockfile, builds it once more + # from the archive to verify it, and leaves it at target/package/. The attestation is of that + # file. Both come before the tag: nothing is published until the artifact exists. + - name: Package the .crate with the lockfile + if: steps.version.outputs.tag != '' + env: + VERSION: ${{ steps.version.outputs.version }} run: | cargo package --locked - sha256sum target/package/float-abel-*.crate | tee target/package/sha256 + (cd target/package && sha256sum float-abel-"$VERSION".crate | tee float-abel-"$VERSION".crate.sha256) - name: Attest the .crate's provenance + if: steps.version.outputs.tag != '' uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2 with: subject-path: target/package/float-abel-*.crate - # cargo publish re-packages the same tree with the same cargo, byte for byte, and uploads it; - # --no-verify skips the second verify build, not the packaging. A missing token fails before - # anything is uploaded, and the digest is re-asserted on whatever cargo leaves behind. - - name: Publish - env: - AUTH: ${{ inputs.auth }} - TRUSTED_TOKEN: ${{ steps.auth.outputs.token }} - SECRET_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} - run: | - case "$AUTH" in - trusted) token="$TRUSTED_TOKEN" ;; - token) token="$SECRET_TOKEN" ;; - *) token="" ;; - esac - if [ -z "$token" ]; then - echo "release: no crates.io token for auth=$AUTH" >&2 - exit 1 - fi - CARGO_REGISTRY_TOKEN="$token" cargo publish --locked --no-verify - if ls target/package/float-abel-*.crate >/dev/null 2>&1; then - sha256sum --check target/package/sha256 - fi - - name: Upload the published .crate onto the tag's GitHub release + - name: Create the tag and the GitHub Release, with the entry as its notes and the .crate attached + if: steps.version.outputs.tag != '' env: GH_TOKEN: ${{ github.token }} - TAG: ${{ inputs.tag }} - run: gh release upload "$TAG" target/package/float-abel-*.crate --clobber + TAG: ${{ steps.version.outputs.tag }} + run: | + gh release create "$TAG" --target "$GITHUB_SHA" --title "$TAG" --notes-file "$RUNNER_TEMP/notes.md" \ + target/package/float-abel-*.crate target/package/float-abel-*.crate.sha256 diff --git a/CHANGELOG.md b/CHANGELOG.md index e5f7800..cb65db3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,146 @@ # Changelog -One section per release, newest first, written by release-plz from the conventional-commit log -since the previous release tag and grouped by commit type; a breaking change is marked. Versions -follow semantic versioning, `cargo-semver-checks` runs on every release PR, and every release on -GitHub carries the `.crate` with its provenance attestation (README § getting it). +One section per release, newest first, written by `scripts/changelog-entry.sh` from the +conventional-commit log since the previous release tag and grouped by commit type; a breaking change +is marked. The version is counted from 0.0.0 over every commit since the first (DESIGN.md § Release), +and every release on GitHub carries the `.crate` with its provenance attestation (README § getting it). ## [Unreleased] + +## [0.30.2](https://github.com/jbrjake/float-abel/compare/v0.0.0...v0.30.2) - 2026-11-02 + +### Added +- *(release)* count the version from 0.0.0 over every commit since the first and cut releases from it with the repository's own scripts +- *(release)* cut releases with release-plz from the log and attach the attested .crate to every GitHub Release +- *(docs)* rewrite README +- *(ci)* publish to crates.io from release.yml on the owner's workflow_dispatch +- *(dbsp)* give Wide and Fixed DBSP's weight traits +- *(differential)* give Wide and Fixed differential-dataflow's weight traits +- *(laws)* add the closure law, check_closure, with a compact-only Exact as its control +- *(differential)* ship the banded SUM idiom as Exact::band and BandedSum::banded_sum +- *(exact)* encode the heap form on the wire and in the rkyv archive as its block behind a tag +- *(exact)* restore the heap arm as the 32-byte niche form behind an alloc feature +- *(wire)* serialise Exact and Wide as one byte string under serde +- *(arbitrary)* implement Arbitrary for Exact, Wide and Fixed +- *(dbsp)* adapt Exact to dbsp 0.364's weight traits behind the dbsp feature, with the UDA example +- *(simd)* add the AVX2 kernel, four doubles a step behind runtime detection +- *(simd)* fold every tier's bulk entries in lane-parallel tiles, with a portable kernel on every target and a NEON kernel behind the simd feature +- *(binned)* deposit the 106-bit product onto the bins, truncated below the unit and refused at Wide's ceiling, with a bulk entry that checks every pair first +- *(golden)* hash every tier's bytes, reads and refusals on the vectors and 10⁴ seeded multisets, commit the claim, and assert it on seven targets +- *(binned)* build Binned as independent carry-save bins on Wide's limb grid, with Add, into_wide, Wide::to_binned, and compile-fail cases that keep it out of the engine weight traits +- *(differential)* adapt Exact to differential-dataflow 0.25 as a diff type, prove it on three workers with serialized exchange, and ignore the closure's two unmaintained advisories by id +- *(laws)* publish the laws kit behind the laws feature and run every exact tier and a naive f64 control through it +- *(wire)* add the canonical wire format with to_bytes and from_bytes on every tier, an Exact decoder that refuses non-canonical words by name, serde carrying the same bytes, and FORMAT.md with vectors +- *(reads)* add to_f32 on every tier and prove to_f64, to_f32 and div_f64 against CPython-generated vectors +- *(types)* convert between the tiers with Wide from Fixed exact under a const assertion, Exact from Wide and Fixed refused past the width, and Wide from Exact refused outside the window +- *(types)* certify Wide and Fixed as repr(C) plain words with pinned layouts and safe constructors from their limbs +- *(exact)* make Exact a 32-byte Copy plain word with i32 special counts and the integer-amount ring, and drop the heap arm and the alloc feature +- *(exact)* publish Exact on every feature set, with the named cap on the operators and a checked API that returns it +- *(exact)* bound the exponent at 2^30 in every build, and tell an exponent overflow apart from a promotion +- *(exact)* add the compact tier, a canonical 127-bit dyadic with checked add and mul, total negation, numeric Ord and the reads, behind the alloc feature +- *(wide)* add Wide, the 34-limb exact f64 sum with signed special counters, total From, group ops, Mul, read-time projection, deposit_product and bitrep limb conformance +- *(fixed)* Fixed — exact-or-refused deposit, quantize, the group ops, Ord and Hash, to_f64 and div_f64 + +### Fixed +- *(release)* anchor the include globs to the crate root +- *(release)* let the lockfile update read the crates.io index, which a runner has not cached +- *(release)* install release-plz 0.4.3 by its tarball's sha256, since 0.4.2's musl build fails on the changelog header +- *(release)* keep release-plz's JSON output out of the working tree it refuses to find dirty +- *(release)* fail the release-plz jobs on release-plz's own exit status and let the release job read pull requests +- *(ci)* let a commit scope carry a comma, as refactor(wide,fixed) does +- *(deps)* clear RUSTSEC-2026-0385, -0391, -0398 and -0403 from the dbsp closure +- *(deps)* move dbsp's floor to 0.367 past zip 6's three advisories +- *(ci)* skip the task store's lint under CI, where no job installs meshwork +- *(exact)* close the limb cap under retraction at its edge +- *(exact)* let the compact add decide on the sum's span, not on the raised operand or the aligned frame +- *(noalloc)* fold the zero with the assign operator so the workspace clippy leg passes +- *(types)* assert the headroom and counter budgets in every build on Wide and Fixed, and run the panic tests under --release in the push gate + +### Performance +- *(exact)* inline the operator chain down to the compact tier + +### Changed +- *(wide,fixed)* move the operator impls into ops submodules +- *(simd)* delete the floating-point Binned kernel, the loser of the A/B, and record the numbers + +### Documentation +- *(release)* record the release ruling: release-plz on dev→main from a v0.0.0 base, an attested .crate in place of a signed tag, crates.io outside the release +- *(changelog)* name the first release by the log, not a number +- *(claude)* record the advisory-trigger and macOS-leg rulings +- *(bench)* re-take the crossover at f24b051 on both machines +- *(bench)* re-take the reproducibility-cost table at f24b051 on both machines +- *(bench)* record the heap arm's budget A/B against the Copy word +- *(bench)* record the Wide row of the end-to-end SUM +- *(overflow)* replace the options and their recommendation with what was decided +- *(exact)* state the heap form's contract in the README and the design +- *(design)* state the heap form's diff row as accepted at 1.9–2.4× beside the Copy word's +- *(overflow)* measure the 32-byte niche form against the Copy word, the enum and the banded idiom on both exchanges, and set the heap arm's budget +- *(overflow)* measure the heap arm as a proxy beside the banded idiom on three bands, and list it as an option with what it reopens +- *(overflow)* record the spike's findings — the two failure classes, the boundary bug, the banded idiom and its measured cost +- *(design)* state the diff row as accepted and the product kernel as held for a consumer +- *(design)* return the diff row and the product sentence to open rulings +- *(readme)* write the outward docs and run the README's Rust blocks as doctests +- *(design)* restate the targets and the diff row to the figures the owner accepted +- *(bench)* record the crossover on both machines and write the decision memo +- *(bench)* re-take every table at 2a82388 and name each section's commit +- *(bench)* record the wire form's three bincode passes per update on both machines +- *(bench)* record the prototype table, the width caps, the product row and the end-to-end SUM +- *(design)* state that deposit_product is refused by name on Fixed and Wide outside the window, and that Exact holds every product +- *(design)* finish the heap-era wording — the word's size, the width cap in the bounds and the kill table, and the dev-dependency list +- *(dbsp)* pin dbsp 0.364.0 on rustc 1.99.0 + +### Tests +- *(exact)* cross six of the eleven cap-edge shapes under miri so the miri job keeps its headroom +- *(binned)* poison the slices' last entry by their length so the refusal case runs under miri +- *(rounding)* scale the random-multiset slice through rounds() so miri runs 200 +- *(laws)* run the ring law at the limb cap's edge +- *(laws)* draw the limb cap's edge in the kit's Exact strategy +- *(laws)* run the four older laws on the full-range Exact strategy +- *(engines)* prove the five histories and product folds on the heap Exact inside differential and DBSP +- *(ui)* pin the compile-fail wording to stable 1.99 with rust-src and skip it elsewhere +- *(miri)* run the suite under miri with bit-built powers of two and scaled sweeps +- *(fuzz)* add the decode, sum-vs-oracle and sub-round-trip cargo-fuzz targets on a num-bigint oracle +- *(golden)* run the transcript with simd on and record every bulk entry, so the hash covers each kernel +- *(wire)* draw random limbs with array::from_fn so the topic stays under the length target +- *(wire)* prove the serde bytes with postcard instead of unmaintained bincode, drop the advisory ignore, and allow Unicode-3.0 for unicode-ident + +### Benchmarks +- *(baseline)* commit the per-machine bench baseline and its script +- *(dd_sum)* add the Wide row on the same-scale fixture +- *(dd_sum)* record the banded idiom beside the plain one on the same-scale and mixed fixtures +- *(dd_sum)* re-take the end-to-end SUM on the heap form beside isize and read the row against the kill line +- *(crossover)* price one change as the group grows, Exact in differential against a plain f64 re-sum +- *(wire)* iterate the tuple form's bytes as a slice, as the crate's impl of it did +- *(wire)* time timely's three bincode passes on a batch of Exact diffs against isize and two candidate forms +- *(dd_sum)* run the end-to-end SUM on typed exchange beside serialized +- add the ops and dd_sum targets and the products row +- *(simd)* build Binned kernel (ii), the floating-point peel, beside kernel (i) and hold it to the fold's bits +- *(simd)* measure each tier's bulk entry against its scalar reference on NEON and AVX2, and start BENCH.md + +### Build +- *(deps)* update rustls-webpki to 0.103.16 and webpki-root-certs to 1.0.10 past RUSTSEC-2026-0382 and -0383 +- *(deps)* bump rustls to 0.23.46 past four advisories in dbsp's closure +- *(dbsp)* take dbsp on a caret like differential's +- *(deny)* check the dbsp closure with its own deny-dbsp.toml and keep deny.toml to the crate's other features + +### CI +- *(release)* cut the release as one pull request, dev into main +- *(gates)* run the release scripts' self-tests in the commit and push gates +- *(release)* cut the release PR from a clone with scripts/release-pr.sh, not a runner +- *(actions)* bump actions/upload-artifact from 4.6.2 to 7.0.2 +- *(actions)* bump actions/cache from 4.3.0 to 6.1.0 +- *(actions)* bump rust-lang/crates-io-auth-action from 1.0.5 to 1.0.6 +- *(actions)* bump actions/checkout from 4.4.0 to 7.0.1 +- *(workflows)* fold five workflows into three, the suite nightly on dev only when it moved and again on the pull request into main +- *(release)* run no ci on a push to dev and open the release PR on the owner's dispatch alone +- *(release)* refuse the release PR while the default branch is not dev +- *(release)* gate commit subjects on the Conventional Commits form, in the commit-msg hook, both gates and the PR +- *(release)* run ci on dev and point the push and release-cut commands at it +- *(deny)* fail both gates when an ignored advisory no longer matches a crate +- *(dbsp)* free the runner's disk and build a changed lockfile from a clean target +- *(actions)* let dependabot refresh the action hashes by PR +- *(golden)* check the wasmtime and cross tarballs against a sha256 before extracting +- *(security)* pin every action to its commit hash and fail the gates on a tag + +### Other +- *(deny)* ignore RUSTSEC-2025-0141 for bincode 1.3.3 by id, the adapter's wire and a dev-dependency its team calls complete diff --git a/CLAUDE.md b/CLAUDE.md index 42228d8..ca38dce 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -18,8 +18,10 @@ cargo fuzz run sum_vs_oracle --target-dir "$CARGO_TARGET_DIR/fuzz" -- -max_total ./scripts/regression.sh # push gate (pre-push hook) git config core.hooksPath .githooks # run the gates from git ./scripts/bench-baseline.sh compare # after a cargo bench: every row past 1.5× of this machine's bench-baseline.json; `write` re-records it -git push origin dev # where work lands; main takes only the dev→main PR that cuts a release -gh pr create --base main --head dev --title "chore(release): cut the release the log yields" # once release-plz's release PR has merged into dev; the owner merges both +git push origin dev # where work lands; a push runs no ci (the push gate ran here); main takes only the dev→main PR +gh workflow run ci.yml --ref dev # the whole suite on demand; nightly it runs by itself, only when dev moved outside docs/meshwork/ +./scripts/release-pr.sh # from this clone on dev at origin/dev, under your gh login: commits the version the log counts to and the entry onto dev, pushes, and opens or refreshes the one PR, dev→main; --dry-run prints it first +./scripts/version-from-log.sh # the version the log counts to at HEAD; prepare-release.sh writes it and the CHANGELOG entry ``` A `run` verify dies at 300 s, so warm `target/` before `meshwork close`. Keep `CARGO_TARGET_DIR` @@ -85,15 +87,20 @@ outside `~/Documents`. something to auto-trigger to remove the exception when dbsp updates zstd"). - Speed targets are measured into `BENCH.md`, never gated, because a missed target is a design question, not a broken build. -- Releases are cut by release-plz on the `dev`→`main` merge, version and changelog derived from the - conventional-commit log since the last tag, with `v0.0.0` on the first manifest commit as the - never-built base (owner: "the base initial value WE NEVER USE is 0.0.0 that IMMEDIATELY GETS THE - CONVENTIONAL COMMITS ADDED TO IT EVEN FOR THE FIRST BUILD!"); a `feat` bumps the minor below 1.0 - too (owner: "of course feats bump the minor"); every GitHub Release carries the `.crate` with a - GitHub artifact attestation in place of a signed tag, and a ruleset pins `v*` tags (owner chose - "Attestation + tag ruleset"). crates.io is outside the release until the owner says (owner: "I - WILL TELL YOU WHEN I WANT IT AND THEN YOU WILL ADD IT TO THE RELEASE PROCESS"). -- A session runs `cargo publish` only with `--dry-run`. `release.yml` publishes on the owner's +- Releases are cut on the `dev`→`main` merge at the version counted from 0.0.0 over every commit + since the first, breaking +major, `feat` +minor, `fix` +patch, by `scripts/version-from-log.sh` + (owner: "YOU NEED TO BE COUNTING FROM 0.0.0 ON FROM _THE VERY FIRST COMMIT_"; "of course feats + bump the minor"); the changelog entry is `scripts/changelog-entry.sh`'s, and the release workflow + runs on git, cargo, gh and the two GitHub-owned actions alone (owner: "THIS IS A MINIMALIST + PROJECT THAT IS SUPPOSED TO HAVE NO DEPENDENCIES IT DOESN'T NEED"). The release is one pull + request, `dev`→`main`, cut from a clone by `scripts/release-pr.sh` under the developer's own gh + login, never by a runner, because a commit and a pull request carry no identity a runner could add + (owner: "why can't it be something done from a clone using gh?"; "i want it to be a single PR, + dev to main."). Every GitHub Release carries + the `.crate` with a GitHub artifact attestation in place of a signed tag, and a ruleset pins `v*` + tags (owner chose "Attestation + tag ruleset"). crates.io is outside the release until the owner + says (owner: "I WILL TELL YOU WHEN I WANT IT AND THEN YOU WILL ADD IT TO THE RELEASE PROCESS"). +- A session runs `cargo publish` only with `--dry-run`. `publish.yml` publishes on the owner's `workflow_dispatch` alone, never on a tag push (owner: "make it a ci publish on my manual trigger"), and nothing in the release runs it; the first publish, the crates.io name reservation included, carries the owner's token, and every later one Trusted Publishing, because crates.io @@ -104,9 +111,14 @@ outside `~/Documents`. that can be manipulated"; "security is absolutely critical here"). - Dependabot refreshes those hashes by PR, one `github-actions` entry in `.github/dependabot.yml` on a weekly schedule, and nothing merges on its own (owner: "yes have dependabot open prs that - update the pinned hashes"). golden.yml's tarball digests are not actions and move by hand. -- golden.yml's `aarch64 macOS` leg runs on every event, whatever it bills, because the committed - hash is blessed on that platform (owner: "keep the mac leg"). + update the pinned hashes"). The golden jobs' tarball digests are not actions and move by hand. +- `ci.yml` is the one test suite, run nightly on `dev` only when a commit since the last nightly + reached anything outside `docs/meshwork/`, and again on the pull request into `main`; a push runs + nothing (owner: "testing to occur nightly, ONLY when there have been code changes since the last + run. a middle path. then, yes, again on the PR"; "A PUSH IS NOT SUPPOSED TO BE AN INTENSIVE + TEST!"). `release.yml` cuts the release and `publish.yml` publishes; there is no fourth. +- The golden `aarch64 macOS` job runs whenever the suite runs, whatever it bills, because the + committed hash is blessed on that platform (owner: "keep the mac leg"). ## Boundaries diff --git a/Cargo.lock b/Cargo.lock index b3c3cab..7ae9513 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1593,7 +1593,7 @@ dependencies = [ [[package]] name = "float-abel" -version = "0.0.0" +version = "0.30.2" dependencies = [ "arbitrary", "bitrep", diff --git a/Cargo.toml b/Cargo.toml index a0197f1..3387ff3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "float-abel" -version = "0.0.0" +version = "0.30.2" edition = "2021" rust-version = "1.74" license = "MIT OR Apache-2.0" @@ -9,7 +9,7 @@ repository = "https://github.com/jbrjake/float-abel" readme = "README.md" categories = ["no-std", "mathematics", "data-structures", "algorithms"] keywords = ["float", "reproducible", "abelian", "differential-dataflow", "dbsp"] -include = ["src/**", "Cargo.toml", "README.md", "FORMAT.md", "LICENSE-MIT", "LICENSE-APACHE"] +include = ["src/**", "/Cargo.toml", "/README.md", "/FORMAT.md", "/LICENSE-MIT", "/LICENSE-APACHE"] # One integration-test target. Topics are flat files under tests/, each a `mod` of tests/main.rs; # discovery is off so a second file can never become a second binary. autotests = false diff --git a/docs/DESIGN.md b/docs/DESIGN.md index 5e5fdf4..99b0d4b 100644 --- a/docs/DESIGN.md +++ b/docs/DESIGN.md @@ -713,8 +713,8 @@ converts to. The transcript is `tests/golden/`, its own workspace member, because the main test target links engine dependencies wasm32 cannot run. It is asserted equal to `tests/data/golden.sha256` on x86_64 Linux and Windows, aarch64 macOS and Linux, wasm32 (wasmtime), and, through QEMU via `cross`, riscv64gc and s390x. s390x is big-endian, which is what catches a -byte-order slip in `from_bytes`. The committed hash is the claim, and the workflow asserts it on every -push. +byte-order slip in `from_bytes`. The committed hash is the claim, and `ci` asserts it whenever the +suite runs: nightly when `dev` moved, and on the pull request into `main`. ## Verification @@ -726,7 +726,7 @@ push. | decode | canonical round-trip; non-canonical bytes refused; a refusal names the first rule FORMAT.md gives; serde agrees with `from_bytes` | unit tests, `cargo-fuzz` nightly (`decode_robustness`) | | sums | `Wide`'s limbs and counts equal a `num-bigint` sum of the multiset bit for bit, inserts and retracts alike; every read equals it rounded in independent integer arithmetic; `Fixed256` accepts exactly its window and holds the same integer; `Exact` agrees wherever the width holds the running sum; `Binned` lands the truncated sum; `a + b − b == a` on every tier over any two canonical `Exact` words and any two multisets | `cargo-fuzz` nightly (`sum_vs_oracle`, `sub_roundtrip`), ten minutes a target | | UB | the lib's unit tests, the no-feature build and the suite under the interpreter, with the statistical sweeps at a hundredth, the cap-edge oracle cross on six of its eleven shapes, trybuild, the timely topics and the laws kit out, and every bulk entry on the portable kernel | `cargo miri test` in CI | -| architecture | one SHA-256 over every tier's bytes, reads and refusals on the vectors and 10⁴ seeded multisets, on 7 targets | the golden-hash workflow | +| architecture | one SHA-256 over every tier's bytes, reads and refusals on the vectors and 10⁴ seeded multisets, on 7 targets | `ci`'s golden jobs | | SIMD | every kernel equals the scalar reference bit for bit on the vectors and 10⁴ seeded multisets (specials and subnormals included); lane-count independence at 2, 4 and 8 partials | the golden hash covers every kernel | | batch tier | a `Binned` fold bit-identical under 1,000 shuffles and 1–64 merge partitions; equal to the sum of the inputs truncated below the window, and so within `n·2^(−64(K−1))·max\|x\|` of `Exact`; `Binned` fails to compile as a differential-dataflow diff or a DBSP weight | `trybuild` | | differential-dataflow | the three-worker integration test; join `Exact × isize`; antijoin with `isize`; `iterate` | feature `differential` | @@ -787,19 +787,29 @@ each release. ## Release {#sp-release} `dev` is where work lands and the default branch; `main` holds releases and accepts only a pull -request from `dev`, merged as a merge commit. release-plz reads the conventional-commit log since the -last release tag and keeps a release PR open against `dev` carrying the version and the CHANGELOG -entry that log yields: a `feat` bumps the minor, a `fix` the patch, a breaking change the minor below -1.0 and the major from 1.0 on, and `cargo-semver-checks` raises the bump a missed `!` would have. The -base it counts from is `v0.0.0` on the commit that laid the crate down, never built; no version is -typed by hand. Merging the release PR fixes the version, and merging `dev` into `main` cuts the -release: release-plz tags `vX.Y.Z` and publishes the GitHub Release with the entry as its notes, and -the release workflow builds the `.crate` from the tag with `cargo package --locked`, attests its -provenance with GitHub artifact attestations (Sigstore, bound to this repository, the workflow and -the commit) and attaches the `.crate`, its sha256 and the attestation to the release, which a reader -checks with `gh attestation verify`. A ruleset keeps `v*` tags from being moved or deleted. Both -merges are the owner's acts; so is any announcement. crates.io is outside the release; the owner -says when it joins. +request from `dev`, merged as a merge commit. The version is counted from 0.0.0 over every commit +since the first, in order, by Conventional Commits' own mapping: a breaking change (`!` after the +type, or a `BREAKING CHANGE:` footer) adds one to the major and zeroes the rest, else a `feat` adds +one to the minor and zeroes the patch, else a `fix` adds one to the patch, and every other type +leaves it alone; `scripts/version-from-log.sh` is that count, and nobody types a version. A push to +`dev` is just a push: the push gate ran on the developer's machine, and the runner does nothing. +Nightly, `ci` runs the whole suite on `dev` when a commit since the last nightly reached anything +outside the task store, and skips the night otherwise. The release is one pull request, `dev` into +`main`. `scripts/release-pr.sh`, run from a clone on `dev` under the developer's own gh login, +writes the count into Cargo.toml and Cargo.lock and the entry for the commits since the last tag into +CHANGELOG.md, grouped by type by `scripts/changelog-entry.sh`, commits the three files onto `dev` as +`chore(release): vX.Y.Z`, pushes, and opens that pull request with the entry as its body, or +refreshes the open one; no runner holds write access to the repository for it, because a commit and +a pull request carry no identity a runner could add, where the attestation below does. The pull +request runs the whole suite, and merging it cuts the release: the workflow refuses a Cargo.toml that +disagrees with the count, builds the `.crate` from that commit with `cargo package --locked`, attests +its provenance with GitHub artifact attestations (Sigstore, bound to this repository, the workflow and +the commit), then tags `vX.Y.Z` and publishes the GitHub Release with the entry as its notes and the +`.crate`, its sha256 and the attestation attached, which a reader checks with `gh attestation +verify`. The build runs on git, cargo and gh +and the two GitHub-owned actions alone, because the crate's posture of no dependency it does not need +is the build's too. A ruleset keeps `v*` tags from being moved or deleted. The merge is the +owner's act; so is any announcement. crates.io is outside the release; the owner says when it joins. ## Non-goals diff --git a/docs/meshwork/archive/fl-05nqrwd-count-the-version-from-0-0-0-over-every-commit-s.md b/docs/meshwork/archive/fl-05nqrwd-count-the-version-from-0-0-0-over-every-commit-s.md new file mode 100644 index 0000000..333cb4c --- /dev/null +++ b/docs/meshwork/archive/fl-05nqrwd-count-the-version-from-0-0-0-over-every-commit-s.md @@ -0,0 +1,30 @@ +--- +id: fl-05nqrwd +title: "Count the version from 0.0.0 over every commit since the first (breaking +major, feat +minor, fix +patch) in a script with fixtures, and drive the release PR and the release from it in place of release-plz" +status: done +category: crate/release +labels: [reproducible-floats] +parent: fl-m3cymxv +verify: "all(exists scripts/version-from-log.sh, absent release-plz.toml, absent .github/workflows/release-plz.yml, lacks CLAUDE.md release-plz)" +docs: + - docs/DESIGN.md#release-sp-release + - CLAUDE.md#standing-decisions +seq: 15 +created: 2026-11-02T17:38Z +--- + +Owner, 2026-11-02, in session, on the release process as built with release-plz (one bump per release, 0.1.0 on today's log): "THERE IS ABSOLUTELY NO WAY THAT OUR VERSION IS 0.1.0! I HAVE TOLD YOU THIS OVER AND OVER! YOU NEED TO BE COUNTING FROM 0.0.0 ON FROM _THE VERY FIRST COMMIT_! I CAN'T BELIEVE YOU SPENT ALL THIS TIME WITHOUT TELLING ME YOU WERE IGNORING THE VERY THING I INSISTED YOU DO!" + +The rule, Conventional Commits' own semver mapping applied to every commit in order from the first: start at 0.0.0; a breaking change (`!` after the type or a `BREAKING CHANGE:` footer) adds one to the major and zeroes the rest; else a `feat` adds one to the minor and zeroes the patch; else a `fix` adds one to the patch; every other type leaves the version alone. Merge commits are skipped; the walk is `git rev-list --reverse --topo-order --no-merges`. Today's log (227 commits) counts to 0.29.3. No release tool does this (release-plz, release-please, git-cliff --bump and cocogitto all take one bump per release from the strongest commit), so release-plz goes. + +Build, red first: `scripts/version-from-log.sh` with a `--self-test` over fixture sequences (each a list of subjects and bodies with the version they must reach) and a default mode that walks the repo and prints the version; `.github/workflows/cut-release.yml` with (a) on push to dev: count V; if Cargo.toml's version or CHANGELOG.md's top entry is not V, write the version into Cargo.toml and Cargo.lock, render the entry for the commits since the last `v*` tag with git-cliff (hash-pinned through taiki-e/install-action's manifest) under `## [V]`, splice it under CHANGELOG.md's `## [Unreleased]` line, commit on a release branch and open or update the PR "chore(release): vV" against dev (RELEASE_PLZ_TOKEN or GITHUB_TOKEN, as before); (b) on push to main: count V, refuse unless Cargo.toml says V (the release PR was not merged first), skip if the tag exists, else `gh release create vV --target $GITHUB_SHA` with the entry as notes, then the package, attest and upload steps as they stand. release-plz.toml and release-plz.yml are removed; CLAUDE.md § Standing decisions, DESIGN.md § Release and CHANGELOG.md's header say the counting rule in the owner's words; the v0.0.0 tag stays as the changelog range's base. Done is the self-test green, the local count printing 0.29.3 at this tree, both jobs green on a dev push and a main push (the dev job ending at the token's 403 counts, as before), and the readback of the consequence to the owner: today's first release is 0.29.3, the next feat makes it 0.30.0, the next fix 0.29.4, and the first breaking change 1.0.0. + +## log +- 2026-11-02T17:38Z created +- 2026-11-02T17:38Z open→doing — claimed by claude (de9bd423-d8b5-4cf3-8a2c-44d698f4cc3e) +- 2026-11-02T17:55Z doing→done — verify exit 0 @ 84a4936+1 + +## comments +- 2026-11-02T17:43Z [claude (de9bd423-d8b5-4cf3-8a2c-44d698f4cc3e)] 2026-11-02, mid-build, owner, in session: "STOP ADDING ALL THESE DEPENDENCIES! I NEVER TOLD YOU TO START INTRODUCING UNTRUSTED DEPENDENCIES INTO OUR BUILD PROCESS! WTF IS GIT-CLIFF? WE NEVER TALKED ABOUT THIS!" and "THIS IS A MINIMALIST PROJECT THAT IS SUPPOSED TO HAVE NO DEPENDENCIES IT DOESN'T NEED! READ. THE. ROOM! DID YOU EVEN READ YOUR OWN README? DO YOU EVEN UNDERSTAND THE PROJECT YOU'RE WORKING ON? STOP. VIOLATING. ITS. SPIRIT!" So: no git-cliff, no release-plz, no taiki-e/install-action, no cargo-semver-checks in the release workflow. The release runs on git, cargo and gh on the runner, actions/checkout and actions/attest-build-provenance (both already pinned here), and the repo's own scripts: version-from-log.sh (the count), changelog-entry.sh (the entry, grouped by type), prepare-release.sh (Cargo.toml, Cargo.lock, CHANGELOG.md). The local release-plz install was removed (cargo uninstall) and the scratch binaries deleted. +- 2026-11-02T17:54Z [claude (de9bd423-d8b5-4cf3-8a2c-44d698f4cc3e)] 2026-11-02 runner findings: run 41495208355 on dev at a72dc9d reached prepare-release.sh and failed in `cargo update --workspace --offline` with 'no matching package named arbitrary found, location searched: crates.io index', because a fresh runner has no index cached where this machine does; 84a4936 drops --offline (the index is read, nothing downloaded; the local dry run moves one lockfile line, float-abel's version). The count at f00a030 is 0.30.0 (the feat that landed the process) and at 84a4936 0.30.1 (its fix): the rule working. From this push on only dev is pushed; main moves by the dev→main PR, so the release job's refusal path runs for the first time at the first merge. +- 2026-11-02T17:55Z [claude (de9bd423-d8b5-4cf3-8a2c-44d698f4cc3e)] 2026-11-02 run 41495586562 on dev at 84a4936: prepare-release.sh wrote 0.0.0 -> 0.30.1 on the runner (CHANGELOG.md +128 lines, Cargo.lock and Cargo.toml one line each), the commit 'chore(release): v0.30.1' is on origin as release/next, and the job ended at 'GitHub Actions is not permitted to create or approve pull requests' from gh pr create: the token's, the owner's act (fl-4fcbtkx item 1). Done per the body: self-tests green, the local count 0.30.1 at this tree, the dev job green through prepare and ending at the token's 403; the release job's refusal path first runs at the first dev→main merge, since main is no longer pushed. diff --git a/docs/meshwork/fl-4fcbtkx-push-main-to-origin-and-make-the-repo-readable-b.md b/docs/meshwork/archive/fl-4fcbtkx-push-main-to-origin-and-make-the-repo-readable-b.md similarity index 71% rename from docs/meshwork/fl-4fcbtkx-push-main-to-origin-and-make-the-repo-readable-b.md rename to docs/meshwork/archive/fl-4fcbtkx-push-main-to-origin-and-make-the-repo-readable-b.md index 21dfbc1..828d706 100644 --- a/docs/meshwork/fl-4fcbtkx-push-main-to-origin-and-make-the-repo-readable-b.md +++ b/docs/meshwork/archive/fl-4fcbtkx-push-main-to-origin-and-make-the-repo-readable-b.md @@ -1,7 +1,7 @@ --- id: fl-4fcbtkx title: "Push main to origin and make the repo readable by leras's CI — git push origin main through the push gate, then flip jbrjake/float-abel public or add it to leras's ORESEUR_TOKEN PAT, both the owner's acts" -status: blocked +status: done category: rulings labels: [reproducible-floats] answers: leras#le-yxhtwgw @@ -12,7 +12,6 @@ seq: 56 created: 2026-10-16T16:51Z verify: contains docs/meshwork/fl-4fcbtkx-push-main-to-origin-and-make-the-repo-readable-b.md /2026-[0-9][0-9]-[0-9][0-9] owner pushed main to origin/ blocked-reason: "step 2, the repo's readability for leras's runner (flip public or add to the ORESEUR_TOKEN PAT), is a GitHub setting on the owner's account; main is on origin at b14d830" -claimed-by: claude (03cbad3b-1e00-455d-9d44-3b0d9decee6e) parent: fl-b9qtzty --- @@ -45,6 +44,7 @@ green, and leras's swap task waits only on leras closing its ask. - 2026-10-16T19:42Z blocked→open - 2026-10-16T19:42Z open→doing — claimed by claude (03cbad3b-1e00-455d-9d44-3b0d9decee6e) - 2026-10-16T19:44Z doing→blocked — step 2, the repo's readability for leras's runner (flip public or add to the ORESEUR_TOKEN PAT), is a GitHub setting on the owner's account; main is on origin at b14d830 +- 2026-11-02T20:37Z blocked→done — verify exit 0 @ 7d78a7a+3 ## comments - 2026-10-16T19:42Z [claude (03cbad3b-1e00-455d-9d44-3b0d9decee6e)] 2026-10-16 owner, in session: "push to origin". Step 1 is on: git push origin main through the push gate from this checkout. Step 2, the repo's readability for leras's runner, is a GitHub setting on the owner's account and is not asked for; the task stays open until the owner does it and writes the sentence the verify reads. @@ -69,3 +69,8 @@ green, and leras's swap task waits only on leras closing its ask. 6. Settings → Environments → release with the owner as required reviewer: release.yml's crates.io dispatch names it; nothing in the release process does, so this waits for the day crates.io joins. 7. About: topics (rust, no-std, floating-point, reproducibility, differential-dataflow, dbsp). 8. Dependabot PRs #1–#4 now base dev; #2 and #3 carry 'Bump' titles the commits job refuses (fl-hp7md5n). +- 2026-11-02T17:51Z [claude (de9bd423-d8b5-4cf3-8a2c-44d698f4cc3e)] 2026-11-02, correcting item 1 of the list above after the rebuild without release-plz (fl-05nqrwd): the secret is RELEASE_PR_TOKEN (a GitHub App installed on this repo with Contents and Pull requests read/write, or a fine-grained PAT with the same), read by .github/workflows/cut-release.yml's release-pr job; without it the job's PR step ends at the same 403 unless Settings → Actions → General allows Actions to create pull requests (then the PR opens with GITHUB_TOKEN and runs no ci). Everything else on the list stands. +- 2026-11-02T18:05Z [claude (de9bd423-d8b5-4cf3-8a2c-44d698f4cc3e)] 2026-11-02, end of session: ci runs on pull requests and main only, a push to dev runs nothing, and the release PR is opened by `gh workflow run cut-release.yml --ref dev` on the owner's word (owner: "A PUSH IS JUST A PUSH"; "DOING THAT WHEN I TELL YOU TO BUILD A PR"). Item 1 stands (RELEASE_PR_TOKEN or the Actions setting) and is what the dispatch needs. Two ci runs were still in progress when the session ended and the session could not cancel them: 41495939738 (dev) and 41491375969 (main); `gh run cancel ` stops each. +- 2026-11-02T18:34Z [claude (227b40e5-20bd-44b0-9b79-d44c1a3c3587)] 2026-11-02, fl-ezhk4cz at 4bec2d5: item 1's workflow is .github/workflows/release.yml (from cut-release.yml), release-pr job; the dispatch is `gh workflow run release.yml --ref dev`. Read this session: `gh api repos/jbrjake/float-abel/rulesets` and `.../branches/main/protection` both answer 403 'Upgrade to GitHub Pro or make this repository public to enable this feature', so until the flip no required check, no PR-only main and no v* tag ruleset can exist; the dev→main PR's checks are what the owner reads before merging by hand. ci.yml's check names changed with the merge: the golden jobs are 'golden ' and the fuzz jobs 'fuzz (10 min)', plus 'code changed since the last nightly'; the required-checks list on the ruleset, when it can be set, names those. +- 2026-11-02T20:06Z [claude (987c488d-43f3-4192-b0e1-6b279735b89c)] 2026-11-02, fl-ebbxwd2 at c9aaa03: item 1 of the settings list (RELEASE_PR_TOKEN, or Settings → Actions → General → "Allow GitHub Actions to create and approve pull requests") is struck. release.yml has no release-pr job and no workflow_dispatch; `scripts/release-pr.sh` cuts the release PR from a clone under the developer's own gh login, and no secret or Actions toggle is wanted for the release (owner: "why do we need a workflow for this? why can't it be something done from a clone using gh?"; "OF COURSE FIX IT!"). Items 2 to 8 stand as written. The branch release/next on origin at ae1c010 is the runner's "chore(release): v0.30.1" from run 41495586562; the script's first real run force-pushes over it. +- 2026-11-02T20:37Z [claude (987c488d-43f3-4192-b0e1-6b279735b89c)] 2026-11-02 owner pushed main to origin and made the repo readable by leras's CI: main has been on origin since 2026-10-16 (b14d830, now 3ac2723), and the owner flipped jbrjake/float-abel public on 2026-11-02 (owner, in session: "i just made the repo public btw"; gh api: private=false, visibility=public, default_branch=dev). What the flip made settable, read the same minute: a ruleset 'branch-protection' (id 26389594) already exists; default workflow permissions read, can_approve_pull_request_reviews=false; secret scanning and push protection show disabled, dependabot security updates disabled. Items 3 to 7 of the 17:26 list remain the owner's acts. diff --git a/docs/meshwork/archive/fl-9hcnr6q-rule-on-the-release-pr-against-dev-keep-it-as-th.md b/docs/meshwork/archive/fl-9hcnr6q-rule-on-the-release-pr-against-dev-keep-it-as-th.md new file mode 100644 index 0000000..4be2230 --- /dev/null +++ b/docs/meshwork/archive/fl-9hcnr6q-rule-on-the-release-pr-against-dev-keep-it-as-th.md @@ -0,0 +1,25 @@ +--- +id: fl-9hcnr6q +title: "Rule on the release PR against dev: keep it as the entry's review on GitHub, or let release-pr.sh commit the release prep straight to dev so the dev→main PR is the one review and one merge" +category: rulings +labels: [reproducible-floats] +parent: fl-m3cymxv +relates: [fl-ebbxwd2] +docs: + - docs/DESIGN.md#release-sp-release +verify: contains docs/meshwork/fl-9hcnr6q-rule-on-the-release-pr-against-dev-keep-it-as-th.md /2026-[0-9][0-9]-[0-9][0-9] owner ruled on the release PR/ +status: done +created: 2026-11-02T19:55Z +--- +The release PR against dev was release-plz's mechanism, carried over when the scripts replaced it. The owner's words asked for one PR: "pr from dev to main to cut one, versions automatically created from conventional commit log tracking" (fl-m3cymxv). Today a release is two PRs and two merges by the owner: the release PR (release/next into dev, the three generated files) and then dev→main. With `scripts/release-pr.sh` cutting the PR from a clone, the entry is readable locally with `--dry-run` before anything is pushed, so the release PR's one job, a GitHub-side read of the number and the entry, is done on the developer's machine. + +The alternative: release-pr.sh commits `chore(release): vX.Y.Z` onto dev itself and pushes dev (a push to dev runs no ci, like every push to dev), and the owner opens and merges dev→main alone. Concretely, for the first cut at 0.30.1: today, `./scripts/release-pr.sh`, merge the release PR on GitHub, `gh pr create --base main --head dev`, merge; with the change, `./scripts/release-pr.sh`, `gh pr create --base main --head dev`, merge. What the change gives up: the release prep commit never sits in a PR of its own, so a wrong entry is caught in `--dry-run` or in the dev→main PR's diff, not in a dedicated review. Nothing else moves: the release job's refusal of a Cargo.toml that disagrees with the count, the tag, the attestation and the Release are unchanged either way. + +A ruling, not a decision: the session built the clone-side script in the current two-PR shape and changed nothing here. When the owner rules, this task's comment carries " owner ruled on the release PR: ", and a `keep` closes it while a `one PR` files the change under fl-m3cymxv. + +## log +- 2026-11-02T19:55Z created +- 2026-11-02T20:35Z open→done — verify exit 0 @ 7d78a7a+1 + +## comments +- 2026-11-02T20:35Z [claude (987c488d-43f3-4192-b0e1-6b279735b89c)] 2026-11-02 owner ruled on the release PR, in session: "i want it to be a single PR, dev to main." The release PR against dev goes; scripts/release-pr.sh commits the release prep onto dev and opens or refreshes the one pull request, dev into main. Filed under fl-m3cymxv as the change this ruling names. diff --git a/docs/meshwork/archive/fl-cnkjdzp-rule-on-the-release-process-release-plz-on-dev-m.md b/docs/meshwork/archive/fl-cnkjdzp-rule-on-the-release-process-release-plz-on-dev-m.md index 9d58b1b..e4e8ce7 100644 --- a/docs/meshwork/archive/fl-cnkjdzp-rule-on-the-release-process-release-plz-on-dev-m.md +++ b/docs/meshwork/archive/fl-cnkjdzp-rule-on-the-release-process-release-plz-on-dev-m.md @@ -62,3 +62,4 @@ the ruled shape (the verify reads the tool's name there). 4. Attestation is the deliverable: on the release cut, the workflow builds the .crate from the tag with `cargo package --locked`, attests it with actions/attest-build-provenance and attaches .crate, sha256 and attestation to the GitHub Release; README's install section says `gh attestation verify`. Proven after the flip, since attestations are public-repo-only on a Free plan. 5. DESIGN.md § Release rewritten to this shape (the "0.1.0 ships once …" list and the signed-tag clause are gone; the owner's merge is the release gate); CLAUDE.md § Standing decisions carries the ruling in the owner's words. 6. Owner acts the build will hand back: a GitHub App or fine-grained PAT as the `RELEASE_PLZ_TOKEN` secret so the release PR runs ci (the workflow falls back to GITHUB_TOKEN until then), `dev` as default branch, the `main` ruleset (PR from dev only, merge commits only, required checks), the `v*` tag ruleset, and the flip public. +- 2026-11-02T17:38Z [claude (de9bd423-d8b5-4cf3-8a2c-44d698f4cc3e)] 2026-11-02, after the build: owner, in session: "THERE IS ABSOLUTELY NO WAY THAT OUR VERSION IS 0.1.0! I HAVE TOLD YOU THIS OVER AND OVER! YOU NEED TO BE COUNTING FROM 0.0.0 ON FROM _THE VERY FIRST COMMIT_!" The derivation in this task's ruling comment (one bump per release, 0.1.0) was the session's misreading of 'gets the conventional commits added to it'; the rule is a running count over every commit since the first, 0.29.3 on today's log. fl-05nqrwd rebuilds the process on it; CLAUDE.md and DESIGN.md are corrected there. diff --git a/docs/meshwork/fl-cqmcs7c-run-ci-s-miri-dbsp-and-golden-jobs-only-when-the.md b/docs/meshwork/archive/fl-cqmcs7c-run-ci-s-miri-dbsp-and-golden-jobs-only-when-the.md similarity index 83% rename from docs/meshwork/fl-cqmcs7c-run-ci-s-miri-dbsp-and-golden-jobs-only-when-the.md rename to docs/meshwork/archive/fl-cqmcs7c-run-ci-s-miri-dbsp-and-golden-jobs-only-when-the.md index 500798c..a4fdc99 100644 --- a/docs/meshwork/fl-cqmcs7c-run-ci-s-miri-dbsp-and-golden-jobs-only-when-the.md +++ b/docs/meshwork/archive/fl-cqmcs7c-run-ci-s-miri-dbsp-and-golden-jobs-only-when-the.md @@ -8,7 +8,7 @@ parent: fl-b9qtzty docs: - docs/DESIGN.md#hardening-sp-hardening verify: "all(contains .github/workflows/ci.yml needs.changes.outputs.rust, contains .github/workflows/golden.yml needs.changes.outputs.rust)" -status: open +status: dropped created: 2026-11-02T15:14Z --- Owner, 2026-11-02: "WHY IS CI TAKING SO LONG". The miri job is the long pole at 41 to 57 minutes @@ -33,3 +33,5 @@ list until the flip's ruleset exists (fl-4fcbtkx's checklist, item 5). ## log - 2026-11-02T15:14Z created +- 2026-11-02T18:02Z open→doing — claimed by claude (de9bd423-d8b5-4cf3-8a2c-44d698f4cc3e) +- 2026-11-02T18:05Z doing→dropped — owner, 2026-11-02, in session: "A PUSH IS NOT SUPPOSED TO BE AN INTENSIVE TEST! A PUSH IS JUST A PUSH!" ci no longer runs on a push to dev at all (only on pull requests and main), so there is no push-time filtering left to build diff --git a/docs/meshwork/archive/fl-ebbxwd2-cut-the-release-pr-from-a-clone-with-gh-scripts-r.md b/docs/meshwork/archive/fl-ebbxwd2-cut-the-release-pr-from-a-clone-with-gh-scripts-r.md new file mode 100644 index 0000000..a955795 --- /dev/null +++ b/docs/meshwork/archive/fl-ebbxwd2-cut-the-release-pr-from-a-clone-with-gh-scripts-r.md @@ -0,0 +1,27 @@ +--- +id: fl-ebbxwd2 +title: "Cut the release PR from a clone with gh: scripts/release-pr.sh in place of release.yml's release-pr job, so no runner holds write access to the repository for a commit and a pull request" +category: crate/release +labels: [reproducible-floats] +seq: 13 +parent: fl-m3cymxv +docs: + - docs/DESIGN.md#release-sp-release + - CLAUDE.md#commands +verify: "all(lacks .github/workflows/release.yml /release-pr:/, lacks .github/workflows/release.yml workflow_dispatch, exists scripts/release-pr.sh, contains CLAUDE.md release-pr.sh, contains docs/DESIGN.md release-pr.sh)" +status: done +created: 2026-11-02T19:55Z +--- +Owner, 2026-11-02, in session: "why do we need a workflow for this? why can't it be something done from a clone using gh?"; "like you're asking me to jump through all these hoops to give a cloud worker broad permissions to do something we could do right here with gh, no?"; "OF COURSE FIX IT!" + +release.yml's release-pr job checked out dev, ran scripts/prepare-release.sh, committed the three files on release/next, force-pushed and ran `gh pr create` or `gh pr edit`. Every step runs from a clone under the developer's own gh login, which already opens pull requests on this repository. The runner added nothing and cost a RELEASE_PR_TOKEN secret or the repository-wide "Allow GitHub Actions to create and approve pull requests" toggle, the reason the job failed three times on 2026-11-02 (runs 41490353635, 41495586562 and the dispatch after fl-ezhk4cz). The shape was release-plz's, kept when the scripts replaced it (fl-05nqrwd) and not re-examined. + +The fix: `scripts/release-pr.sh` does what the job did, from a temporary worktree at origin/dev so the developer's checkout and branches are untouched: prepare-release.sh, the commit `chore(release): vX.Y.Z` on a detached head, `git push --force origin HEAD:refs/heads/release/next`, then `gh pr edit` or `gh pr create` against dev with the entry as the body. `--dry-run` stops before the push and prints the version, the diff and the entry. `--self-test` runs the whole path against a bare fixture origin with gh stubbed through `$GH`: the PR opens, then refreshes, then nothing to do once dev carries the version, then a dry run pushes nothing. release.yml loses the job, the `workflow_dispatch` trigger and the token comment; the release job on the push to main stays, because the attestation signs with the workflow's OIDC identity, which a clone cannot produce. CLAUDE.md § Commands names the script in place of `gh workflow run release.yml --ref dev`; DESIGN.md § Release says who cuts the PR and why no runner does; fl-4fcbtkx's settings list loses item 1. + +## log +- 2026-11-02T19:55Z created +- 2026-11-02T19:56Z open→doing — claimed by claude (987c488d-43f3-4192-b0e1-6b279735b89c) +- 2026-11-02T20:06Z doing→done — verify exit 0 @ 329fa23+6 + +## comments +- 2026-11-02T20:06Z [claude (987c488d-43f3-4192-b0e1-6b279735b89c)] 2026-11-02 built at c9aaa03 (the script, the workflow, CLAUDE.md, DESIGN.md) and 329fa23 (the gates; the self-test made hermetic). Proven here: `./scripts/release-pr.sh --self-test` green (6 s), and red on three deliberate breaks in scratch copies (the commit subject reworded: "release/next carries 'chore: release v0.1.1'"; the push moved ahead of the dry-run check: "the dry run moved release/next"; the edit branch disabled: "no pr edit 7 in the gh log"). `./scripts/release-pr.sh --dry-run` against origin: prepare-release wrote 0.0.0 -> 0.30.1, CHANGELOG.md +134, Cargo.lock and Cargo.toml one line each, the body opening `## [0.30.1](.../compare/v0.0.0...v0.30.1) - 2026-11-02`; nothing pushed, `git worktree list` back to the one checkout, `git status` showing only the session's edits. release.yml parses (ruby -ryaml: one job, one trigger), check-actions-pinned.sh passes, no `release-pr:`, `workflow_dispatch`, `RELEASE_PR_TOKEN` or `pull-requests` left in it. Found on the gates commit: the commit hook exports GIT_INDEX_FILE and GIT_DIR, and the self-test's fixture repositories inherited them; with absolute paths (a scratch clone) the fixture's `git add -A` staged into the enclosing repository's index, and `git init` re-initialised it. The self-test now unsets every GIT_* before building fixtures, and passes plain, under relative and under absolute hook variables in a scratch clone with that clone's index and branches untouched. Not run: the script without --dry-run, which opens the real release PR and is the owner's word (fl-enkdnvk step 1); gh's behaviour from the detached worktree is avoided by running gh from the checkout root after the push. diff --git a/docs/meshwork/archive/fl-ezhk4cz-harmonize-the-workflows-into-three-ci-yml-the-on.md b/docs/meshwork/archive/fl-ezhk4cz-harmonize-the-workflows-into-three-ci-yml-the-on.md new file mode 100644 index 0000000..d22d398 --- /dev/null +++ b/docs/meshwork/archive/fl-ezhk4cz-harmonize-the-workflows-into-three-ci-yml-the-on.md @@ -0,0 +1,67 @@ +--- +id: fl-ezhk4cz +title: "Harmonize the workflows into three: ci.yml, the one suite, nightly on dev only when it moved and on the PR into main; release.yml, the release; publish.yml, the crates.io publish" +status: done +category: crate/release +labels: [reproducible-floats] +parent: fl-b9qtzty +relates: [fl-c8jpmpm, fl-fs9vhnd, fl-m3cymxv] +verify: "all(exists .github/workflows/ci.yml, exists .github/workflows/release.yml, exists .github/workflows/publish.yml, absent .github/workflows/fuzz.yml, absent .github/workflows/golden.yml, absent .github/workflows/cut-release.yml, contains .github/workflows/ci.yml schedule:, lacks .github/workflows/ci.yml /^\\s+push:/, contains .github/workflows/ci.yml /gh run list/, contains .github/workflows/release.yml /^\\s+push:/)" +docs: + - docs/DESIGN.md#release-sp-release +seq: 10 +created: 2026-11-02T18:24Z +--- + +Owner, 2026-11-02, in session: "we need a simple, best-practice ci/release process. i've given very +clear guidance and you keep giving me broken stuff. you just overcorrected and gave me a ci process +that has no testing until a PR is cut, after first going too granular and doing it on every push. +what we need is testing to occur nightly, ONLY when there have been code changes since the last run. +a middle path. then, yes, again on the PR. please review what we've got and streamline and +harmonize it. there are six different workflows there now and it's not clear what they all are or +when or how they run, and i am skeptical this isn't just careless mess. this is supposed to be a +project that demonstrates best practices in all respects." + +What is there at bfa0173: five workflow files and dependabot.yml. ci.yml (gate, msrv, deny, dbsp, +miri; on push to main, every pull request, dispatch), golden.yml (seven targets; the same three +triggers), fuzz.yml (three targets; those three plus a nightly schedule that fuzzed main for ten +minutes a target every night of October whether or not it moved), cut-release.yml (the release PR +on dispatch, the tag and GitHub Release on a push to main) and release.yml (the crates.io publish on +dispatch). Three files share one trigger set and one purpose, testing the code; two carry names a +reader cannot tell apart; and nothing runs on dev between a push and the release PR. + +Build: three workflows, each one purpose, each header saying when it runs. + +- `ci.yml`, the one suite: gate, msrv, deny, dbsp, miri, golden's seven targets and fuzz's three, + ten minutes each. Triggers: `schedule` nightly on dev (the default branch), `pull_request` into + main, `workflow_dispatch`. No `push`. A first job, `changes`, decides the nightly: the last + completed scheduled run on dev that ended success or failure names the commit it ran at (`gh run + list`, GitHub's own tool), and a `git diff --quiet -- . ':!docs/meshwork'` that is + quiet means no code changed and every other job is skipped; a pull request or a dispatch always + runs. `permissions: contents: read` at the top, `actions: read` on the one job that lists runs. +- `release.yml`, the release, from cut-release.yml: unchanged in what it does, with the .crate + packaged and attested before the tag and the GitHub Release are created, so nothing is published + until the artifact exists. +- `publish.yml`, the crates.io publish, from release.yml: renamed, its header pointing at + release.yml for the GitHub Release and naming itself for the Trusted Publishing form. + +Docs in the same commits: CLAUDE.md's commands and standing decisions, DESIGN.md § Release and +§ Bit-identical across architectures and the Verification table, scripts/regression.sh's and +tests/golden/main.rs's comments, dependabot.yml's comment. The store's tasks that name fuzz.yml or +golden.yml in their verify (fl-c8jpmpm, fl-fs9vhnd) get their verify repointed at ci.yml. + +Proof: the push gate green on each commit; `ruby -ryaml` parses every file; +scripts/check-actions-pinned.sh passes; the `changes` job's two commands run locally against two +real commits, one store-only (quiet) and one that reached a workflow (not quiet); one +`workflow_dispatch` of ci.yml on dev watched to a conclusion with `gh run view`, every job +present. The nightly's skip path is proven by the first two nights: the first runs, and the second, +if dev did not move outside the store, skips with the `changes` job's line in its log. + +## log +- 2026-11-02T18:24Z created +- 2026-11-02T18:25Z open→doing — claimed by claude (227b40e5-20bd-44b0-9b79-d44c1a3c3587) +- 2026-11-02T19:15Z doing→done — verify exit 0 @ dca52bd+1 + +## comments +- 2026-11-02T18:36Z [claude (227b40e5-20bd-44b0-9b79-d44c1a3c3587)] 2026-11-02 built at 4bec2d5 on dev, pushed through the regression gate (regression: OK; bfa0173..4bec2d5). The push started no workflow of ours, as designed. Proven locally: ruby -ryaml parses ci.yml, release.yml and publish.yml; check-actions-pinned.sh passes; the changes job's diff is quiet across the store-only 84a4936..1887f18 and not across 1887f18..0e81d36; its gh run list query returns empty for ci.yml's schedule and f24b051 for the old fuzz.yml's, the sha the store recorded for October's idle main. GitHub now lists exactly ci, release and publish (plus Dependabot Updates); fuzz, golden and cut-release are gone from the registry. The proof run is the dispatch 41502146344 on dev at 4bec2d5, https://github.com/jbrjake/float-abel/actions/runs/41502146344; its conclusion goes in the next comment. The nightly's skip path cannot be dispatched (the changes job reads the event name) and is proven by the first two nights: 03:17 UTC 2026-11-03 runs, and 2026-11-04 skips if dev did not move outside docs/meshwork/. +- 2026-11-02T19:15Z [claude (227b40e5-20bd-44b0-9b79-d44c1a3c3587)] 2026-11-02 proof: dispatch 41502146344 on dev at 4bec2d5, conclusion=success on attempt 2. Attempt 1 ended red on one job, golden aarch64 macOS, cancelled after 15 min with GitHub's annotation 'The job was not acquired by Runner of type hosted even after multiple attempts' under a 'capacity constraints ... macOS arm64' notice; the other 16 jobs were green; `gh run rerun --failed` re-ran that job alone and it passed in 21 s. Per job: changes <1, gate 6, msrv <1, deny 1, dbsp 5, miri 34, golden Linux/Windows/macOS/aarch64 ≤2 each, wasm32 2, riscv64gc 1, s390x 2, fuzz 11 ×3; the commits job skipped (not a pull request). About 100 billed minutes with warm caches (Linux ×1, Windows ×2, macOS ×10). The changes job's log line: 'ci: a workflow_dispatch runs the suite'. The schedule path's first firing is 03:17 UTC 2026-11-03. diff --git a/docs/meshwork/fl-fs9vhnd-set-every-workflow-s-token-to-read-only-at-the-t.md b/docs/meshwork/archive/fl-fs9vhnd-set-every-workflow-s-token-to-read-only-at-the-t.md similarity index 51% rename from docs/meshwork/fl-fs9vhnd-set-every-workflow-s-token-to-read-only-at-the-t.md rename to docs/meshwork/archive/fl-fs9vhnd-set-every-workflow-s-token-to-read-only-at-the-t.md index 95e779b..5c04def 100644 --- a/docs/meshwork/fl-fs9vhnd-set-every-workflow-s-token-to-read-only-at-the-t.md +++ b/docs/meshwork/archive/fl-fs9vhnd-set-every-workflow-s-token-to-read-only-at-the-t.md @@ -1,14 +1,14 @@ --- id: fl-fs9vhnd -title: "Set every workflow's token to read-only at the top: permissions: contents: read in ci.yml, fuzz.yml and golden.yml" +title: "Set every workflow's token to read-only at the top: permissions in ci.yml, release.yml and publish.yml" category: crate/hardening labels: [reproducible-floats] seq: 50 parent: fl-b9qtzty docs: - CLAUDE.md#standing-decisions -verify: "all(contains .github/workflows/ci.yml /^permissions:/, contains .github/workflows/fuzz.yml /^permissions:/, contains .github/workflows/golden.yml /^permissions:/)" -status: open +verify: "all(contains .github/workflows/ci.yml /^permissions:/, contains .github/workflows/release.yml /^permissions:/, contains .github/workflows/publish.yml /^permissions:/)" +status: done created: 2026-11-02T15:14Z --- release.yml sets `permissions: {}` and grants per job; ci.yml, fuzz.yml and golden.yml set nothing @@ -20,3 +20,7 @@ top-level `contents: read` is that same token spelled out. ## log - 2026-11-02T15:14Z created +- 2026-11-02T18:34Z open→done — verify exit 0 @ 4bec2d5+2 + +## comments +- 2026-11-02T18:34Z [claude (227b40e5-20bd-44b0-9b79-d44c1a3c3587)] 2026-11-02 done inside fl-ezhk4cz at 4bec2d5: ci.yml carries `permissions: contents: read` at the top, and its changes job alone adds `actions: read` for `gh run list`; release.yml (from cut-release.yml) and publish.yml (from release.yml) keep `permissions: {}` at the top with per-job grants. fuzz.yml and golden.yml are gone: their jobs live in ci.yml under its top-level grant, and the fuzz artifact upload runs under that read-only token as the body said it would. Verify repointed at the three files that exist. diff --git a/docs/meshwork/archive/fl-hp7md5n-rebase-and-merge-dependabot-s-four-actions-prs-o.md b/docs/meshwork/archive/fl-hp7md5n-rebase-and-merge-dependabot-s-four-actions-prs-o.md new file mode 100644 index 0000000..3926a33 --- /dev/null +++ b/docs/meshwork/archive/fl-hp7md5n-rebase-and-merge-dependabot-s-four-actions-prs-o.md @@ -0,0 +1,35 @@ +--- +id: fl-hp7md5n +title: "Rebase and merge dependabot's four actions PRs once main is green: checkout v7.0.1, cache v6.1.0, upload-artifact v7.0.2, crates-io-auth-action v1.0.6" +category: crate/hardening +labels: [reproducible-floats] +seq: 75 +parent: fl-b9qtzty +needs: [fl-r0s86hg] +docs: + - CLAUDE.md#standing-decisions +verify: lacks .github/workflows/ci.yml 11d5960a326750d5838078e36cf38b85af677262 +status: done +created: 2026-11-02T15:14Z +--- +PRs #1 to #4 are red on stale bases, not on their changes: #2's base b14d830 still exec'd the +meshwork shim from smoke.sh (`exit 127`, since skipped under CI); #1 and #3 at 4a83a00 hit the miri +index-out-of-bounds at tests/binned_product.rs:426 that c385a17 fixed; #4 at f24b051 hit the dbsp +advisories (fl-r0s86hg). Each PR keeps the hash-plus-version-comment form and the gate's pinning +check passed on #1 and #3, so the standing decision holds through them. + +Once fl-r0s86hg lands on main: `@dependabot rebase` on each (a comment on GitHub, so the owner's +word first, or the weekly schedule does it), watch the ci, golden and fuzz checks green, and merge +in the order #1 (checkout, touches all four workflows), #3 (cache), #2 (upload-artifact), #4 +(crates-io-auth-action). checkout v7 and cache v6 run on node24; ubuntu-latest has it. Merging is +the owner's act on the repo; the verify reads the old checkout hash gone from ci.yml. + +## log +- 2026-11-02T15:14Z created +- 2026-11-02T19:19Z open→doing — claimed by claude (55cb23a1-b965-4570-9fea-74159b1286bc) +- 2026-11-02T19:27Z doing→done — verify exit 0 @ 510de5e+1 + +## comments +- 2026-11-02T17:21Z [claude (de9bd423-d8b5-4cf3-8a2c-44d698f4cc3e)] 2026-11-02 dependabot re-targeted its four PRs at dev when dev became the default branch (#1–#4 now base dev). ci's new commits job checks each PR's commit subjects and title against scripts/check-commit-message.sh, which wants a lowercase subject: #1 and #4 say 'bump' and pass, #2 and #3 carry dependabot's older 'Bump' and will fail that job until dependabot recreates them (a '@dependabot recreate' comment, or the weekly run after closing them) or the owner merges them by hand. Merging needs the owner's token: the PRs are dependabot's, and the merge is a write to dev. +- 2026-11-02T18:34Z [claude (227b40e5-20bd-44b0-9b79-d44c1a3c3587)] 2026-11-02, fl-ezhk4cz at 4bec2d5: ci.yml now triggers on `pull_request` into main alone, so dependabot's PRs into dev run no checks; their merge is a push to dev, tested by the next nightly. The `commits` job no longer runs on them either, but it runs on the dev→main PR over every non-merge commit in the range, so a 'Bump' subject (#2, #3) merged into dev as a merge commit fails there. Merge each with a conforming subject: squash with the PR title (dependabot's titles are lowercase 'bump' on #1 and #4; retitle #2 and #3 by hand or `@dependabot recreate`), or rebase-merge after a recreate. The verify stands: the old checkout hash gone from ci.yml. +- 2026-11-02T19:27Z [claude (55cb23a1-b965-4570-9fea-74159b1286bc)] 2026-11-02, owner in session: "handle the dependabot PRs". Merged on GitHub in the order #1, #3, #2, #4 as merge commits bfaa5e6, c88cca3, f3019de, 510de5e on dev (the repo allows merge commits only: squash and rebase merges are off, so the squash-with-title route in the earlier comment was not available). Dependabot had already recreated all four at 4bec2d5 with lowercase 'bump' subjects, so every non-merge commit passes check-commit-message.sh --range and both version-from-log.sh and changelog-entry.sh skip merge commits: the version stays 0.30.1 across the range. Each hash was resolved from its tag through the GitHub API before merging and matched: checkout v7.0.1 3d3c42e, cache v6.1.0 55cc834, upload-artifact v7.0.2 cf430e0, crates-io-auth-action v1.0.6 3268eb4. Release notes read: checkout v7.0.0 blocks fork checkouts under pull_request_target and workflow_run, which ci.yml does not use; v6 moved persisted credentials out of .git/config (release.yml sets persist-credentials false anyway); cache v6 and upload-artifact v7 are ESM ports on node24, which the hosted runners carry. The merge went through GitHub, not the local push gate; the gate's two workflow checks, check-actions-pinned.sh and check-commit-message.sh, ran green here on the merged tree, and the Rust tree is unchanged. The first run on the new pins is tonight's nightly, dev having moved outside docs/meshwork/. diff --git a/docs/meshwork/archive/fl-jkb64s3-run-the-release-scripts-self-tests-in-both-gates.md b/docs/meshwork/archive/fl-jkb64s3-run-the-release-scripts-self-tests-in-both-gates.md new file mode 100644 index 0000000..ea1f105 --- /dev/null +++ b/docs/meshwork/archive/fl-jkb64s3-run-the-release-scripts-self-tests-in-both-gates.md @@ -0,0 +1,22 @@ +--- +id: fl-jkb64s3 +title: "Run the release scripts' self-tests in both gates: version-from-log.sh, changelog-entry.sh and release-pr.sh beside check-commit-message.sh's" +category: crate/release +labels: [reproducible-floats] +seq: 14 +parent: fl-m3cymxv +relates: [fl-ebbxwd2] +docs: + - CLAUDE.md#commands +verify: "all(contains scripts/smoke.sh version-from-log.sh, contains scripts/smoke.sh changelog-entry.sh, contains scripts/smoke.sh release-pr.sh, contains scripts/regression.sh version-from-log.sh, contains scripts/regression.sh changelog-entry.sh, contains scripts/regression.sh release-pr.sh)" +status: done +created: 2026-11-02T19:55Z +--- +Found filing @clone: `scripts/version-from-log.sh --self-test` and `scripts/changelog-entry.sh --self-test` run in neither scripts/smoke.sh nor scripts/regression.sh; only `check-commit-message.sh --self-test` does (smoke.sh and regression.sh, unconditionally). The version the release carries is counted by the first and its notes rendered by the second, so a regression in either reaches the release PR before anything runs the fixtures. ci.yml does not run them either. + +The fix: in smoke.sh, the three release scripts' `--self-test` under the leg that runs when `scripts/*` is in the diff (`rust=1`), since release-pr.sh's fixture builds a git repository and runs `cargo update` and the commit gate is scoped to the diff; in regression.sh, all three beside check-commit-message's, unconditionally. Each self-test prints `