Repository navigation
Release 0.3.0 #21
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Copyright (c) 2026 Web Ascender. All rights reserved. Internal use only. | |
| # | |
| # WHY THIS EXISTS, beyond habit: this library's entire thesis is a set of forcing | |
| # functions -- coverage_spec, completeness_spec, job_correlation_spec, | |
| # pagination_spec, archive_spec, redaction_spec -- and CLAUDE.md says outright that | |
| # "a change that makes any of those pass more easily is a regression". They force | |
| # nothing if they only run when someone remembers. A library whose cardinal rule is | |
| # "nothing goes missing without saying so" needs something saying so about itself. | |
| name: CI | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| workflow_dispatch: | |
| concurrency: | |
| group: ci-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| spec: | |
| name: rspec (Ruby ${{ matrix.ruby }}, Rails ${{ matrix.rails }}, PostgreSQL ${{ matrix.postgres }}) | |
| runs-on: ubuntu-latest | |
| strategy: | |
| # Every leg always runs: which combination broke is the whole point, so | |
| # stopping at the first failure would throw away most of the answer. | |
| fail-fast: false | |
| matrix: | |
| # 3.3 is the FLOOR (required_ruby_version), and it is here because testing | |
| # only the development version leaves that claim unverified -- an adopting | |
| # app would be the one to discover it was wrong. | |
| # | |
| # Which is exactly what happened: this leg was added as "3.2", matching the | |
| # gemspec, and failed on SecureRandom.uuid_v7 being 3.3+. DESIGN §2.1 had | |
| # said 3.3 was the hard floor all along; the gemspec was simply wrong. If | |
| # this leg fails again, fix the code or raise the floor -- never drop it. | |
| # | |
| # Unpinned patch on purpose: 3.3.0 exactly cannot run Rails 8.1 (actionview | |
| # uses `yield(*, **)` inside a block, which its parser rejects), so pinning | |
| # would test a configuration nobody can use. | |
| # | |
| # 4.0.6 is what the library is developed on, and what .ruby-version and the | |
| # reference application pin. | |
| ruby: ["3.3", "4.0.6"] | |
| # 16 is the FLOOR DESIGN §20 claims, and it is here for the same reason | |
| # the 3.3 Ruby leg is: a floor nothing runs against is a guess. §20 says | |
| # the design "targets PG 16 and requires nothing newer", and a code audit | |
| # agrees -- the newest thing used anywhere is gen_random_uuid() (PG 13) in | |
| # the benchmark task, and the partitioning is all PG 11-era. But agreeing | |
| # with a grep is not the same as passing, which is the whole argument for | |
| # the Ruby floor leg. | |
| # | |
| # If this leg fails, fix the code or raise the floor in DESIGN §20, the | |
| # README and CLAUDE.md together. Dropping it is not one of the options. | |
| # | |
| # 18 is what the library is developed on, and what §20.2 records real wins | |
| # from -- eager page freezing on insert-only partitions above all. | |
| postgres: ["16", "18"] | |
| # 8.0 is the FLOOR `rails ~> 8.0` claims, and it is here for the third | |
| # time on the same argument: an unrun floor is a guess. This one had | |
| # already been WRITTEN DOWN as a guess -- CLAUDE.md carried the standing | |
| # note "Unverified claim, deliberately left standing: `rails ~> 8.0` | |
| # admits 8.0, but CI tests 8.1.3.1 only", and the 0.1.0 CHANGELOG listed | |
| # it under Known gaps. It was not a hypothetical gap either: Rails 8.0 | |
| # has no `Rails.event`, so AuditLog.notify takes a DIFFERENT BRANCH there | |
| # (falling back to calling the subscriber directly), and that branch was | |
| # the one every adopter on the floor would use and the only one nothing | |
| # ran. event_transport_spec asserts which branch is taken, in both | |
| # directions, so this leg proves the fallback works rather than merely | |
| # that nothing raised. | |
| # | |
| # Adding it found one real breakage immediately: spec/dummy pinned | |
| # `config.load_defaults 8.1`, which raises `Unknown version "8.1"` on 8.0 | |
| # before a single example loads. It now tracks the resolved Rails. | |
| # | |
| # "latest" is UNPINNED on purpose and is not a synonym for 8.1: it is the | |
| # newest Rails the gemspec admits, so when 8.2 ships this leg tests it | |
| # without anybody editing this file. Pinning both ends would quietly stop | |
| # testing the ceiling the day it moved. | |
| rails: ["8.0", "latest"] | |
| # Six legs, not eight. Rails 8.0 runs on the two combinations worth | |
| # having -- every floor at once (3.3 / PG 16), and the floor Rails on the | |
| # development Ruby and Postgres -- while `latest` keeps the full 2x2 it | |
| # has always had. The excluded pair would only re-test a middle. | |
| exclude: | |
| - ruby: "3.3" | |
| postgres: "18" | |
| rails: "8.0" | |
| - ruby: "4.0.6" | |
| postgres: "16" | |
| rails: "8.0" | |
| services: | |
| # Both ends of the supported range, per DESIGN §20: 16 because that is the | |
| # floor the design claims, 18 because that is what it is developed on. | |
| # | |
| # The gem itself uses nothing newer than PG 13. uuidv7() is PG 18 and is | |
| # used by the REFERENCE APP's seeds, not by this library -- spec/dummy has | |
| # no seeds at all. §20.3 records that ALTER TABLE ... MERGE PARTITIONS does | |
| # not exist in any version, which is why rollup_year! is a hand-rolled | |
| # copy-and-swap; that is a constraint, not a dependency. | |
| postgres: | |
| image: postgres:${{ matrix.postgres }} | |
| env: | |
| POSTGRES_USER: postgres | |
| POSTGRES_PASSWORD: postgres | |
| POSTGRES_DB: postgres | |
| ports: | |
| - 5432:5432 | |
| options: >- | |
| --health-cmd pg_isready | |
| --health-interval 10s | |
| --health-timeout 5s | |
| --health-retries 10 | |
| env: | |
| RAILS_ENV: test | |
| # Read by the Gemfile. Empty means "resolve normally", which is the | |
| # `latest` leg -- see the note there and the one in the Gemfile. | |
| RAILS_VERSION: ${{ matrix.rails == '8.0' && '~> 8.0.0' || '' }} | |
| # spec/dummy/config/database.yml reads all four, defaulting to the local | |
| # non-standard port 5438. CI is an ordinary 5432. | |
| AUDIT_LOG_DB_HOST: localhost | |
| AUDIT_LOG_DB_PORT: 5432 | |
| AUDIT_LOG_DB_USER: postgres | |
| AUDIT_LOG_DB_PASSWORD: postgres | |
| steps: | |
| - uses: actions/checkout@v5 | |
| # A PostgreSQL 18 CLIENT on every leg, including the 16 one. pg_dump refuses | |
| # to dump a server NEWER than itself but is happy with an older one, so the | |
| # 18 client covers both matrix legs while the runner's stock client-16 would | |
| # fail the 18 leg. The runner image ships | |
| # postgresql-client-16, and pg_dump refuses to dump a server newer than | |
| # itself -- "aborting because of server version mismatch". That is a real | |
| # hazard for this library rather than a CI detail: schema_format = :sql is | |
| # mandatory here, so pg_dump is on the normal migration path, and the | |
| # engine's PGTZ initializer exists precisely to control what it renders. | |
| - name: Install the PostgreSQL 18 client | |
| run: | | |
| sudo install -d /usr/share/keyrings | |
| curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc \ | |
| | sudo gpg --dearmor -o /usr/share/keyrings/pgdg.gpg | |
| echo "deb [signed-by=/usr/share/keyrings/pgdg.gpg] http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" \ | |
| | sudo tee /etc/apt/sources.list.d/pgdg.list | |
| sudo apt-get update | |
| sudo apt-get install -y postgresql-client-18 libpq-dev | |
| echo "/usr/lib/postgresql/18/bin" >> "$GITHUB_PATH" | |
| - uses: ruby/setup-ruby@v1 | |
| with: | |
| ruby-version: ${{ matrix.ruby }} | |
| # No Gemfile.lock is committed -- correct for a gem, whose dependencies | |
| # must resolve against whatever the host app pins -- so there is nothing | |
| # to key a bundler cache on. | |
| bundler-cache: false | |
| - name: Install dependencies | |
| run: bundle install --jobs 4 --retry 3 | |
| # Fail loudly and early on the wrong server version rather than letting a | |
| # partition or planner spec fail obscurely twenty minutes later. | |
| # Asserted separately from the server version, because they fail differently | |
| # and a mismatched CLIENT is the one that cost a build. | |
| - name: Assert the pg_dump client is 18 | |
| run: | | |
| echo "pg_dump: $(pg_dump --version)" | |
| pg_dump --version | grep -qE " 18\." \ | |
| || { echo "::error::pg_dump is not 18; schema dumps will refuse to run."; exit 1; } | |
| - name: Assert the PostgreSQL version under test | |
| run: | | |
| version=$(PGPASSWORD=postgres psql -h localhost -p 5432 -U postgres -d postgres -tAc "SHOW server_version") | |
| echo "server_version = $version" | |
| case "$version" in | |
| ${{ matrix.postgres }}*) ;; | |
| *) echo "::error::Expected PostgreSQL ${{ matrix.postgres }}, got $version. See DESIGN.md §20."; exit 1 ;; | |
| esac | |
| # Same discipline as the PostgreSQL version assertion above: a matrix leg | |
| # that silently resolved the wrong Rails would report a floor as tested | |
| # when it was not -- the exact failure this leg exists to close. | |
| - name: Assert the Rails version under test | |
| run: | | |
| version=$(bundle exec ruby -e 'require "rails"; print Rails::VERSION::STRING') | |
| echo "Rails $version" | |
| case "${{ matrix.rails }}" in | |
| latest) ;; | |
| *) case "$version" in | |
| ${{ matrix.rails }}.*) ;; | |
| *) echo "::error::Expected Rails ${{ matrix.rails }}.x, got $version."; exit 1 ;; | |
| esac ;; | |
| esac | |
| - name: Prepare the dummy app's database | |
| working-directory: spec/dummy | |
| # db:create + db:migrate, NOT db:prepare. structure.sql is deliberately | |
| # git-ignored for this disposable app (db:migrate loads it in preference to | |
| # re-running migrations, so a schema edit silently does nothing), which | |
| # means the migrations are the only source of truth here. | |
| run: bundle exec bin/rails db:create db:migrate | |
| - name: rspec | |
| run: bundle exec rspec --format progress | |
| # The forcing function, through the rake path rather than the spec path. | |
| # Both go through AuditLog::Coverage, and running each proves the wiring. | |
| - name: audit_log:coverage | |
| working-directory: spec/dummy | |
| run: bundle exec bin/rails audit_log:coverage | |
| # A gemspec warning is how a gem ships with an empty licence field or an | |
| # unbuildable file list. This gem is proprietary and must never be pushed, so | |
| # `gem build` is the only packaging step that ever runs -- keep it clean. | |
| # | |
| # DELIBERATELY STRICT, and it has already earned its keep: it caught the | |
| # open-ended `rails >= 8.0` dependency, and only on the older Ruby leg, | |
| # because that rubygems is stricter than the one 4.0.6 ships. If a future | |
| # rubygems adds an advisory warning, the response is to fix the gemspec or | |
| # consciously narrow this check -- not to delete it. | |
| - name: gem build must be warning-free | |
| run: | | |
| output=$(gem build audit_log.gemspec 2>&1) | |
| echo "$output" | |
| if echo "$output" | grep -qi "WARNING"; then | |
| echo "::error::gem build emitted warnings." | |
| exit 1 | |
| fi | |
| # LICENSE.txt must travel inside the built gem: it is the only thing stating | |
| # the terms to whoever ends up holding the .gem file. | |
| - name: Packaged gem carries the licence | |
| run: | | |
| tar -xOf audit_log-*.gem data.tar.gz | tar -tzf - | grep -qx "LICENSE.txt" \ | |
| || { echo "::error::LICENSE.txt is missing from the packaged gem."; exit 1; } |