Skip to content

core: own the execute-ordering ticket, so no exit path can strand it … #667

core: own the execute-ordering ticket, so no exit path can strand it …

core: own the execute-ordering ticket, so no exit path can strand it … #667

Workflow file for this run

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