core: own the execute-ordering ticket, so no exit path can strand it … #667
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
| name: Drift guard | |
| # Gates for the "a list that stopped matching what it describes" class of bug. | |
| # Every job here is fast and dependency-free; none of them compiles anything. | |
| # | |
| # Sections below: the spec <-> code gates, the ladder rung-list gate, then the | |
| # scenario-coverage gate. | |
| # | |
| # Two independent gates for the "spec <-> code drift" class of bug (a | |
| # docs/spec/*.md file stating a mechanical fact -- an enum cardinality, a | |
| # constant, a canonical error string, a glaze parsing flag -- that silently | |
| # stops matching the code): | |
| # | |
| # 1. The compiled pinned-facts test (tests/test_pinned_facts.cpp) needs no | |
| # job here: it is already part of the `morph_tests` target, so it runs | |
| # in every job of the main CI workflow (.github/workflows/ci.yml) for | |
| # free, and benefits from running under every compiler in that matrix | |
| # (GCC, Clang, MSVC, clang-cl) since the enum-cardinality switch pins | |
| # (see tests/test_pinned_facts.cpp) rely on compiler-specific warning | |
| # behavior that is worth exercising on all of them, not just one. | |
| # 2. The prose-vs-manifest lint (scripts/check_spec_citations.sh) is a | |
| # repo-wide text scan, unrelated to compilation -- it runs here, in its | |
| # own fast, dependency-free job. | |
| # | |
| # Unlike .github/workflows/spec-sync.yml, this gate has no "no docs update" | |
| # label escape hatch: a wrong citation or a reintroduced banned term is never | |
| # a legitimate state to merge, so there is nothing to opt out of. | |
| on: | |
| push: | |
| branches: [main, master] | |
| pull_request: | |
| # Supersede a run a newer commit on the same ref has made obsolete -- see the | |
| # note in ci.yml. Cheap here, but the queue it shares is account-wide. | |
| concurrency: | |
| group: drift-guard-${{ github.ref }} | |
| cancel-in-progress: true | |
| permissions: | |
| contents: read | |
| jobs: | |
| prose-lint: | |
| name: Spec-citation & banned-terminology lint | |
| runs-on: ubuntu-24.04 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| # Same terms as rung-filter-lint below, and ci.yml's deprecation-lint and | |
| # test-type-name-lint: a lint gate nobody tests reports green whether or | |
| # not it still detects anything. The section-citation check (morph#316) | |
| # exists because this very script carried a check that could not fail, so | |
| # it is asserted to still fail before it is trusted to pass. | |
| - name: Self-test the spec-citation checker | |
| run: bash scripts/test_check_spec_citations.sh | |
| - name: Run prose-vs-manifest lint | |
| run: bash scripts/check_spec_citations.sh | |
| # ── Ladder rung list <-> the CI filters that are supposed to track it ── | |
| # examples/rungs.txt is the ladder's single authoritative rung list, and | |
| # almost every consumer now derives from it at run time. Two cannot -- | |
| # wasm-ladder.yml's `on.*.paths` (GitHub reads them before any step exists to | |
| # generate them) and codecov.yml (read by Codecov, not by us) -- so they are | |
| # checked against it here. | |
| # | |
| # This job has no path filter, deliberately. The bug it guards against | |
| # (morph#179) was a path filter that had gone stale; gating the guard on one | |
| # would reproduce the failure mode it exists to catch. | |
| rung-filter-lint: | |
| name: Ladder rung list vs. CI path filters | |
| runs-on: ubuntu-24.04 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| # The gate itself is tested first, on the same terms as ci.yml's | |
| # deprecation-lint and test-type-name-lint jobs: a lint gate nobody tests | |
| # reports green whether or not it still detects anything -- and a filter | |
| # that detected nothing is precisely the defect this gate exists for. | |
| - name: Self-test the rung-filter checker | |
| run: bash scripts/test_check_rung_filters.sh | |
| - name: Check every known rung appears in the filters that cannot generate it | |
| run: bash scripts/check_rung_filters.sh | |
| # ── The wire/action surface <-> the scenario corpus that covers it ────── | |
| # scripts/scenario/scenario_coverage.py enumerates two surfaces straight out | |
| # of the source -- every envelope kind and refusal RemoteServer can put on | |
| # the wire (include/morph/core/remote.hpp, wire.hpp), and every action each | |
| # server rung registers (BRIDGE_REGISTER_ACTION under examples/<rung>/) -- | |
| # and diffs both against what scripts/scenario/scenarios/ actually sends and | |
| # asserts. It compiles nothing and starts nothing, which is what lets it run | |
| # here rather than in the build matrix. | |
| # | |
| # The drift it catches is the ordinary kind: a new action registered with no | |
| # scenario reaching it, a new refusal added to remote.hpp that nothing | |
| # asserts, an allowlist entry left behind after the thing it exempted became | |
| # coverable (audited in both directions, so a stale entry fails too), or a | |
| # scenario edited until it no longer qualifies as a workflow. Every one of | |
| # those leaves the corpus quietly measuring less than it did, which is | |
| # exactly the failure mode the report was built for and the reason it is | |
| # worth a gate rather than a habit. | |
| # | |
| # What this job does NOT do is *run* the corpus: that needs the five | |
| # ladder_<rung>_server binaries built, which is a build this workflow does | |
| # not have and would stop being fast and dependency-free to acquire. Running | |
| # scripts/scenario/run_scenarios.py in CI is its own change -- see the PR | |
| # that added the corpus. | |
| # | |
| # No path filter, on the same reasoning as rung-filter-lint above: the | |
| # surface this tracks is spread across include/, examples/ and | |
| # scripts/scenario/, and a filter that missed one of them would reproduce the | |
| # drift it exists to catch. | |
| scenario-coverage: | |
| name: Wire & action surface vs. the scenario corpus | |
| runs-on: ubuntu-24.04 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| # The gate itself is tested first, on the same terms as prose-lint and | |
| # rung-filter-lint above: a lint gate nobody tests reports green whether | |
| # or not it still detects anything. This self-test drives | |
| # scenario_coverage.py against fixtures whose right answers are known -- | |
| # including one where the correct exit code is non-zero -- and pins the | |
| # real corpus's own exit code, so a change that silently stops the | |
| # shipped scenarios covering their own surface fails here. | |
| # | |
| # Standard library only, and no server: scripts/scenario/README.md's | |
| # "Self-test" section states the same. | |
| - name: Self-test the scenario runner and the coverage report | |
| run: python3 scripts/scenario/test_morph_scenario.py | |
| - name: Check every envelope kind, refusal and registered action is covered or exempt | |
| run: python3 scripts/scenario/scenario_coverage.py |