feat(mcp): the degraded-parse routing reaches the MCP refusals — one … #217
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: CI | |
| # Guardrails as automated gates (CLAUDE.md "The guardrails as automated gates"): | |
| # - release job: configure (portable default, no RIPWIRE_NATIVE — CI hardware is not "this dev machine") | |
| # + build + the full gate suite (170+ gates incl. determinism/G4/quality) on SIX legs: | |
| # build-flavour (Release / plain) x front end (macOS AppleClang, Linux gcc, Linux | |
| # clang) — see the job comment for why each axis exists — run in parallel via | |
| # test/pargates.py -j 3 (same authoritative list as regression.sh). | |
| # ubuntu-24.04 here is also the FIRST real proof of L1's portability fix: real x86-64 | |
| # Linux hardware, not the local RIPWIRE_PRETEND_LINUX proxy test/portablebuildcheck.sh | |
| # uses on the (Apple Silicon) dev machines this repo is normally built on. | |
| # - rhel job: the platform this project ships a Linux prebuilt for, checked at PR time instead of only | |
| # at release. RHEL 9 userland (ubi9) via `container:` on a hosted Ubuntu runner, built with | |
| # Red Hat's own gcc-toolset (RHEL 9's default gcc 11 cannot do C++23). Build + fixture | |
| # self-run + the two headline guarantees, NOT the full suite — see the job comment for why. | |
| # - asan job: configure -DRIPWIRE_ASAN=ON (G1: address,undefined,integer,float-divide-by-zero, | |
| # float-cast-overflow, fail-fast) + build + the fixture self-run + the 5 heaviest verbs | |
| # (--pack-task/--from-trace/--quality-delta/--merge-scout/--edit-check) | |
| # via their own gate scripts pointed at the ASan binary. NOT the full regression.sh under | |
| # ASan — that would run 170+ mostly-non-heavy gates at sanitizer speed for little extra | |
| # signal; the release job already proves those gates pass on a normal binary per OS. | |
| # - both jobs end with an explicit 2-run byte-diff det-gate + xmllint --noout, even though | |
| # test/regression.sh already covers both, so a CI log reader sees the two headline product | |
| # guarantees (determinism, well-formed XML / G4) called out as their own named steps. | |
| # | |
| # There is no dependency-download step and no dependency cache: tree-sitter core, all 15 grammars and | |
| # doctest are VENDORED under third_party/deps (see CMakeLists.txt), so `actions/checkout` is the whole | |
| # provisioning story. That also means CI exercises the same hermetic build a stranger's clone gets — | |
| # a cache step would have hidden a re-introduced network fetch behind a warm runner. | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| workflow_dispatch: | |
| concurrency: | |
| group: ci-${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| # Never RIPWIRE_NATIVE in CI: -march=native would bake in whatever ISA the CI runner's host happens to | |
| # expose that week, defeating the entire point of testing the portable default. | |
| jobs: | |
| # ─── style: one gating clang-format check, one advisory clang-format report, one advisory clang-tidy ─── | |
| # | |
| # CLANG_VERSION is a PIN, not a floor. clang-format's output changes across major releases, so an | |
| # unpinned checker reports "unformatted" on a tree that was formatted correctly by a different release. | |
| # .clang-format was authored and verified against clang-format 22 on the dev machine; scripts/formatcheck.sh | |
| # refuses to run under any other major, so bumping this number without re-running `clang-format -i` over the | |
| # gated file set fails loudly instead of silently re-styling the repo. | |
| # | |
| # The clang-format GATE covers only the files listed in scripts/formatcheck.sh, which are the ones that | |
| # already match .clang-format byte for byte. It cannot cover the tree: the house style in CONTRIBUTING.md §3 | |
| # is hand-formatted in ways clang-format has no option to preserve (multi-statement one-liners, | |
| # `for( … ) if( … ) return i;`, packed initialiser rows and `case` labels, hand-chosen 160-200 column wrap | |
| # seams), and reformatting all 98 first-party C++ files changes 11837 lines that survive `git diff -w` — | |
| # real joins and splits — across 89 of them. A whole-tree --Werror check would therefore be red on a | |
| # correctly-styled tree, which is a gate that punishes the documented style. The --advisory step below | |
| # prints that gap on every run so it stays visible rather than forgotten. | |
| # | |
| # clang-tidy is ADVISORY and must stay that way: continue-on-error, and .clang-tidy's WarningsAsErrors is | |
| # empty. Its default catalogue argues for a different C++ than the data-oriented one G2 mandates (POD/SoA, | |
| # C arrays, 32-bit handles, VERIFY instead of exceptions), so the config is curated down to bugprone-* / | |
| # clang-analyzer-* / performance-* / misc-dangling-* and even those only report. Baseline at the commit | |
| # that added it: 312 unique sites, of which 126 are bugprone-easily-swappable-parameters and 85 are | |
| # bugprone-exception-escape. Read it as a to-triage list, not a queue of defects. | |
| style: | |
| name: style (clang-format gate + advisory clang-tidy) | |
| runs-on: ubuntu-24.04 | |
| env: | |
| CLANG_VERSION: "22" | |
| steps: | |
| # L4 (Linux probe): checkout@v4 defaults to a --depth 1 clone, which leaves ONE commit of history | |
| # in the tree. The churn / co-change / ownership gates (churnjoincheck, hotspots, --owners, the | |
| # quality short-horizon-churn kind) mine `git log` for real, so on a shallow checkout they do not | |
| # error — they measure zero and fail, or worse, pass while measuring nothing. Full history it is. | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 # churn/co-change gates read real git history — a shallow clone reddens them | |
| - name: Install clang-format / clang-tidy (PINNED major — see the job comment) | |
| run: | | |
| wget -qO /tmp/llvm.sh https://apt.llvm.org/llvm.sh | |
| chmod +x /tmp/llvm.sh | |
| sudo /tmp/llvm.sh "$CLANG_VERSION" all | |
| clang-format-"$CLANG_VERSION" --version | |
| clang-tidy-"$CLANG_VERSION" --version | |
| # GATING. Runs clang-format --dry-run --Werror over scripts/formatcheck.sh's file list. | |
| # Local equivalent, proven on the dev machine before this step was written: | |
| # CLANG_FORMAT=/opt/homebrew/opt/llvm/bin/clang-format scripts/formatcheck.sh | |
| - name: clang-format — gate (scripts/formatcheck.sh) | |
| run: CLANG_FORMAT=clang-format-"$CLANG_VERSION" bash scripts/formatcheck.sh | |
| # NON-GATING by construction: --advisory always exits 0. It names every first-party C++ file | |
| # clang-format would rewrite, so the size of the ungated remainder is on the record every run. | |
| - name: clang-format — advisory report over the whole first-party set (non-gating) | |
| run: CLANG_FORMAT=clang-format-"$CLANG_VERSION" bash scripts/formatcheck.sh --advisory | |
| # embedded_queries.h and version.h are written at CONFIGURE time, not build time, so a configure | |
| # is the whole provisioning story for a compile database — no compile needed to lint. | |
| - name: Configure a compile database for clang-tidy | |
| run: cmake -S . -B build-tidy -DCMAKE_EXPORT_COMPILE_COMMANDS=ON | |
| - name: clang-tidy — ADVISORY, never gates | |
| continue-on-error: true | |
| run: | | |
| clang-tidy-"$CLANG_VERSION" -p build-tidy --quiet \ | |
| src/main.cpp src/ingest.cpp src/pagerank.cpp src/tsprobe.cpp src/infra/diagnostics.cpp | |
| release: | |
| # Two independent flavours per OS, each on its OWN runner — they used to run stacked in one job, | |
| # doubling wall time for no coverage gain: | |
| # Release — defines NDEBUG, which compiles DEGRADED_PATH_ALERT out; the optimizer-visible build. | |
| # plain — NDEBUG off, so DEGRADED_PATH_ALERT compiles in and the degrade-path gates can observe | |
| # the alert they assert. The e7405e7 qsnap fix was invisible to CI before this flavour | |
| # existed. estchargecheck #14 / qualitystalecheck arms 7/8c probe the flavour (an | |
| # already-gated degrade path + --version's build-type token) and SKIP honestly on the | |
| # Release leg, pointing at the plain leg as where the alert is proven; a missing alert | |
| # on the plain flavour still FAILS. Neither flavour alone is enough. | |
| # | |
| # And BOTH FRONT ENDS on Linux, not just the platform default. gcc is the one this job used to have, | |
| # and it is a feature — a second front end over the same tree, which found three real portability bugs | |
| # on its first run. But it is not the whole story either: RIPWIRE_OPT_REMARKS and RIPWIRE_PGO are | |
| # Clang-only by construction (-Rpass=/-fsave-optimization-record, .profdata/llvm-profdata) and | |
| # CMakeLists FATAL_ERRORs on anything else, so on a gcc-only Linux leg optremarkscheck's configure arms | |
| # cannot be expressed and can only skip. macOS covers Clang but not Linux's libc, headers or linker. | |
| # Six legs = {macos-14 AppleClang, ubuntu gcc, ubuntu clang} x {Release, plain}: every guardrail is | |
| # observed by at least one leg on the OS it matters for, and no leg has to assert the impossible. | |
| name: release (${{ matrix.os }}, ${{ matrix.flavor }}, ${{ matrix.cc }}) | |
| strategy: | |
| fail-fast: false | |
| # Explicit include list rather than three crossed axes: there is no gcc on the macOS runners (`g++` | |
| # there is an AppleClang shim, so a `cc: gcc` macOS leg would silently be a second AppleClang leg | |
| # claiming to be gcc), and a leg that lies about its toolchain is worse than a leg that is missing. | |
| matrix: | |
| include: | |
| - { os: macos-14, flavor: Release, cc: appleclang } | |
| - { os: macos-14, flavor: plain, cc: appleclang } | |
| - { os: ubuntu-24.04, flavor: Release, cc: gcc } | |
| - { os: ubuntu-24.04, flavor: plain, cc: gcc } | |
| - { os: ubuntu-24.04, flavor: Release, cc: clang } | |
| - { os: ubuntu-24.04, flavor: plain, cc: clang } | |
| runs-on: ${{ matrix.os }} | |
| steps: | |
| # L4 (Linux probe): checkout@v4 defaults to a --depth 1 clone, which leaves ONE commit of history | |
| # in the tree. The churn / co-change / ownership gates (churnjoincheck, hotspots, --owners, the | |
| # quality short-horizon-churn kind) mine `git log` for real, so on a shallow checkout they do not | |
| # error — they measure zero and fail, or worse, pass while measuring nothing. Full history it is. | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 # churn/co-change gates read real git history — a shallow clone reddens them | |
| # clang is installed on BOTH Linux legs, not only the clang one. optremarkscheck's Clang-only | |
| # configure arms pin `clang++` when the default front end is not Clang, so the gcc leg keeps that | |
| # coverage instead of skipping it — the same remedy the asan job applies for G1's Clang-only | |
| # sanitizer members. It is a package install, not a change of the leg's own compiler: the gcc leg | |
| # still BUILDS ripwire with gcc, which is the point of having it. | |
| - name: Install tooling (Linux) | |
| if: runner.os == 'Linux' | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y --no-install-recommends libxml2-utils ripgrep bc clang | |
| - name: Install tooling (macOS) | |
| if: runner.os == 'macOS' | |
| run: brew install ripgrep | |
| # CC/CXX are set for the clang leg ONLY. Leaving them unset elsewhere is deliberate: the gcc leg and | |
| # the macOS leg must each get their platform's own default, which is what a stranger's clone gets. | |
| - name: Configure (portable — no RIPWIRE_NATIVE; empty build type = the plain flavour) | |
| env: | |
| CC: ${{ matrix.cc == 'clang' && 'clang' || '' }} | |
| CXX: ${{ matrix.cc == 'clang' && 'clang++' || '' }} | |
| run: cmake -S . -B build ${{ matrix.flavor == 'Release' && '-DCMAKE_BUILD_TYPE=Release' || '' }} | |
| # The front end is recorded per leg so a log reader never has to infer it from the job name alone — | |
| # a `cc: clang` leg whose CMake cache says GNU is a broken matrix, not a passing run. | |
| - name: Confirm the front end this leg actually configured with | |
| run: | | |
| grep -h 'CMAKE_CXX_COMPILER_ID\|CMAKE_CXX_COMPILER_VERSION' build/CMakeFiles/*/CMakeCXXCompiler.cmake | |
| - name: Build | |
| run: cmake --build build -j | |
| # test/pargates.py runs the SAME authoritative gate list regression.sh holds (manifestcheck.sh | |
| # keeps that list honest) in parallel, with the timing-sensitive edit-check budget isolated via | |
| # its `exclusive` group — the exact runner CLAUDE.md prescribes locally. Sequential regression.sh | |
| # cost ~26 min per suite pass; parallel at -j 3 measures ~15-18 min (the 4-vCPU runners saturate — | |
| # many gates are internally multi-threaded), and the flavour matrix keeps the two passes off each | |
| # other's critical path. | |
| - name: "gate suite, parallel (170+ gates: determinism, cache transparency, G4 XML, quality, …)" | |
| run: python3 test/pargates.py . build/ripwire -j 3 | |
| - name: det-gate — 2-run byte-identical diff | |
| run: | | |
| ./build/ripwire test/fixture --no-cache >/tmp/run_a.xml | |
| ./build/ripwire test/fixture --no-cache >/tmp/run_b.xml | |
| diff -q /tmp/run_a.xml /tmp/run_b.xml | |
| - name: G4 — xmllint --noout | |
| run: ./build/ripwire test/fixture --no-cache | xmllint --noout - | |
| # ─── rhel: the platform this project actually ships a prebuilt for, checked at PR time ─────────────── | |
| # | |
| # GitHub hosts no RHEL runner image, but a job can run inside RHEL userland ON a hosted Ubuntu runner via | |
| # `container:`. UBI (Universal Base Image) pulls from registry.access.redhat.com with no subscription, | |
| # which is what makes this work on public CI — release.yml's `smoke-rhel` job already relies on it. | |
| # | |
| # WHY IT IS WORTH A LEG. release.yml builds the Linux artifact in a manylinux_2_28 container and smoke-runs | |
| # it on ubi9 precisely because a binary built on stock ubuntu-24.04 needs GLIBCXX_3.4.31 and DIES on RHEL 9 | |
| # (verified, and recorded in that workflow's own comment). But that check only runs when a release is cut: | |
| # every ordinary PR was tested on glibc 2.39 Ubuntu and macOS only, so a RHEL-incompatible change stayed | |
| # invisible until publish time. This leg moves that signal to the pull request. | |
| # | |
| # SCOPE, STATED HONESTLY. It builds and runs the two headline product guarantees (determinism, well-formed | |
| # XML) plus the fixture self-run — NOT the 361-gate suite. That is deliberate, not laziness: the suite needs | |
| # ripgrep, which is not in the RHEL repositories (it lives in EPEL), and the RHEL-specific signal here is | |
| # "does this toolchain compile it and does this userland run it", which the ubuntu legs cannot give and | |
| # which these steps do give. The full suite stays on the four ubuntu legs and the two macOS ones. | |
| rhel: | |
| name: rhel (ubi9, ${{ matrix.flavor }}) | |
| runs-on: ubuntu-24.04 | |
| container: registry.access.redhat.com/ubi9/ubi | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| flavor: [Release, plain] | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| # RHEL 9's default gcc is 11, which does not implement C++23 — CMakeLists sets CXX_STANDARD 23 with | |
| # STANDARD_REQUIRED ON, so the configure would fail outright. gcc-toolset-N is Red Hat's own supported | |
| # answer (a parallel-installable newer GCC in /opt/rh). Probe newest-first rather than pinning one | |
| # version: which toolsets a given UBI9 AppStream carries changes over the RHEL 9.x lifecycle, and a | |
| # hard pin turns a routine repo refresh into a red leg. Whichever is found is ECHOED, so the log always | |
| # records which compiler actually built this leg rather than leaving it to be inferred. | |
| - name: Install the RHEL build toolchain | |
| run: | | |
| set -eu | |
| dnf install -y cmake git libxml2 python3 make diffutils findutils | |
| found="" | |
| for v in 14 13 12; do | |
| if dnf install -y "gcc-toolset-$v-gcc-c++"; then | |
| found="$v" | |
| break | |
| fi | |
| done | |
| [ -n "$found" ] || { echo "no gcc-toolset-{14,13,12} in this UBI9 AppStream — C++23 is not buildable here"; exit 1; } | |
| echo "TOOLSET=$found" >> "$GITHUB_ENV" | |
| - name: Record the toolchain this leg is using | |
| run: | | |
| echo "gcc-toolset-$TOOLSET" | |
| "/opt/rh/gcc-toolset-$TOOLSET/root/usr/bin/g++" --version | |
| cmake --version | head -1 | |
| cat /etc/redhat-release | |
| # CC/CXX point straight at the toolset's binaries rather than `source .../enable`, because every `run:` | |
| # step is its own shell — a sourced environment would not survive to the next step. | |
| - name: Configure (portable — no RIPWIRE_NATIVE; empty build type = the plain flavour) | |
| env: | |
| CC: /opt/rh/gcc-toolset-${{ env.TOOLSET }}/root/usr/bin/gcc | |
| CXX: /opt/rh/gcc-toolset-${{ env.TOOLSET }}/root/usr/bin/g++ | |
| run: cmake -S . -B build ${{ matrix.flavor == 'Release' && '-DCMAKE_BUILD_TYPE=Release' || '' }} | |
| - name: Build | |
| run: cmake --build build -j | |
| - name: Self-run on the fixture (it parses and ranks under RHEL userland) | |
| run: ./build/ripwire test/fixture --no-cache | head -c 400; echo | |
| - name: det-gate — 2-run byte-identical diff | |
| run: | | |
| ./build/ripwire test/fixture --no-cache >/tmp/run_a.xml | |
| ./build/ripwire test/fixture --no-cache >/tmp/run_b.xml | |
| diff -q /tmp/run_a.xml /tmp/run_b.xml | |
| - name: G4 — xmllint --noout | |
| run: ./build/ripwire test/fixture --no-cache | xmllint --noout - | |
| asan: | |
| name: asan (${{ matrix.os }}) | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [macos-14, ubuntu-24.04] | |
| runs-on: ${{ matrix.os }} | |
| env: | |
| # Darwin's arm64 runtime rejects the standalone leak sanitizer at startup (CMakeLists.txt's own | |
| # comment on this, `ripwire_asan_fixture`); Linux gets full leak detection. | |
| # matrix.os, not runner.os: the runner context does not exist at job-level env — GitHub's | |
| # parser rejects the whole workflow (found by the first real Actions run; local YAML lint | |
| # cannot see it). Step-level `if: runner.os` below is legal and stays. | |
| # log_path: a sanitizer abort inside a gate that pipes 2>/dev/null leaves NO report in the log | |
| # (CI round 3: an asan/x86-64 abort read as "pin no longer resolves"). With log_path the report | |
| # lands in a file the failure artifact below preserves. | |
| ASAN_OPTIONS: ${{ matrix.os == 'macos-14' && 'detect_leaks=0:halt_on_error=1:abort_on_error=1:log_path=/tmp/asanlog' || 'detect_leaks=1:halt_on_error=1:abort_on_error=1:log_path=/tmp/asanlog' }} | |
| UBSAN_OPTIONS: halt_on_error=1:print_stacktrace=1:log_path=/tmp/asanlog | |
| LSAN_OPTIONS: suppressions=${{ github.workspace }}/lsan_suppressions.txt | |
| steps: | |
| # L4 (Linux probe): checkout@v4 defaults to a --depth 1 clone, which leaves ONE commit of history | |
| # in the tree. The churn / co-change / ownership gates (churnjoincheck, hotspots, --owners, the | |
| # quality short-horizon-churn kind) mine `git log` for real, so on a shallow checkout they do not | |
| # error — they measure zero and fail, or worse, pass while measuring nothing. Full history it is. | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 # churn/co-change gates read real git history — a shallow clone reddens them | |
| - name: Install tooling (Linux) | |
| if: runner.os == 'Linux' | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y --no-install-recommends libxml2-utils ripgrep bc clang | |
| - name: Install tooling (macOS) | |
| if: runner.os == 'macOS' | |
| run: brew install ripgrep | |
| # CLANG, not the default cc. G1's `integer` group is Clang-only and gcc rejects the whole option | |
| # ("unrecognized argument to '-fsanitize=' option: 'integer'" — the first public CI run). CMakeLists.txt | |
| # now filters the Clang-only members out under GCC, but that is the HONEST-DEGRADE path for a | |
| # contributor, not something public CI should be running: pinning clang here keeps the COMPLETE G1 | |
| # stack on Linux, so the gates below mean the same thing on both matrix legs. | |
| # | |
| # Deliberately NOT applied to the release job: gcc there is a FEATURE. It is a second front end over | |
| # the same tree and it found three real portability bugs on its first run. | |
| - name: Configure (G1 sanitizer stack — clang, see the step comment) | |
| if: runner.os == 'Linux' | |
| env: | |
| CC: clang | |
| CXX: clang++ | |
| run: cmake -S . -B asan -DRIPWIRE_ASAN=ON | |
| - name: Configure (G1 sanitizer stack) | |
| if: runner.os == 'macOS' | |
| run: cmake -S . -B asan -DRIPWIRE_ASAN=ON | |
| - name: Build | |
| run: cmake --build asan -j | |
| - name: G1 fixture self-run (ripwire_asan_fixture) | |
| run: cmake --build asan --target ripwire_asan_fixture | |
| - name: Heavy verb — --pack-task (test/packtaskcheck.sh) | |
| run: RIPWIRE_BIN=asan/ripwire bash test/packtaskcheck.sh | |
| - name: Heavy verb — --from-trace (test/tracecheck.sh) | |
| run: RIPWIRE_BIN=asan/ripwire bash test/tracecheck.sh | |
| - name: Heavy verb — --quality-delta (test/qualitycheck.sh) | |
| run: RIPWIRE_BIN=asan/ripwire bash test/qualitycheck.sh | |
| - name: Heavy verb — --merge-scout (test/mergescoutcheck.sh) | |
| run: RIPWIRE_BIN=asan/ripwire bash test/mergescoutcheck.sh | |
| - name: Heavy verb — --edit-check (test/editcheckcheck.sh) | |
| run: RIPWIRE_BIN=asan/ripwire bash test/editcheckcheck.sh | |
| # cachefuzzcheck's two mutation tables ARE sanitizer sweeps: they drive corrupt/truncated/hostile cache | |
| # blobs and corrupt qsnap blobs through the readers and assert that no sanitizer report fires. That needs | |
| # an instrumented binary — RIPWIRE_ASAN_BIN is the whole point of the gate — and the `release` jobs, which | |
| # configure only build/, cannot supply one. Until now the gate hard-FAILED there for the missing binary | |
| # (PR #1, run 30732976779: "absorb gate (cachefuzzcheck.sh failed)" on BOTH release legs, that one line | |
| # its only failure) while NO job anywhere ran the sweeps it exists for. The gate now skips those two arms | |
| # with a named reason on a leg that has no ASan build, and this step is where they actually run: both | |
| # variables point at the instrumented binary, so the ground-truth arms and the sanitizer arms agree by | |
| # construction. Adding it here is what makes that skip reason true rather than an excuse. | |
| - name: Sanitizer sweep — corrupt cache / qsnap blobs (test/cachefuzzcheck.sh) | |
| run: RIPWIRE_BIN=asan/ripwire RIPWIRE_ASAN_BIN=asan/ripwire bash test/cachefuzzcheck.sh | |
| # NOT a heavy verb — here because it is the ONLY gate whose subject is an integer-overflow class that | |
| # a plain binary cannot observe. cppqualcheck §9 drives an unbalanced `operator>` frame through | |
| # --from-trace; before the H4 W2b fixup that aborted the G1 build (rc=134) while the plain build ran | |
| # it in silence. The suite job above runs the whole suite TWICE and both times PLAIN, so this gate was | |
| # green-by-construction in CI until it appeared here (V3-H-2). | |
| - name: Sanitizer-only class — cppqualcheck §9 operator-frame scan (test/cppqualcheck.sh) | |
| run: RIPWIRE_BIN=asan/ripwire bash test/cppqualcheck.sh | |
| - name: det-gate — 2-run byte-identical diff (ASan binary) | |
| run: | | |
| ./asan/ripwire test/fixture --no-cache >/tmp/run_a.xml | |
| ./asan/ripwire test/fixture --no-cache >/tmp/run_b.xml | |
| diff -q /tmp/run_a.xml /tmp/run_b.xml | |
| - name: G4 — xmllint --noout (ASan binary) | |
| run: ./asan/ripwire test/fixture --no-cache | xmllint --noout - | |
| # Self-dogfood, STILL NON-GATING — but for a smaller and better-measured reason than before. | |
| # | |
| # The verb now separates the historical floor from the rot: a failed anchor the AUTHOR dated is | |
| # kind="dated-record" and counts in dated=, leaving drift= for live claims. On this repo's own docs | |
| # that splits 104 failed anchors into 63 rot + 41 records, and BOTH constants this comment used to | |
| # name (`kMcpVerbCount = 22`, `kCacheVersion=7`) are now records — their authors had already written | |
| # the "as of <date>" hedge the lane reads. | |
| # | |
| # `|| true` stays, and note first that it was never suppressing an exit code: --doc-drift always | |
| # exits 0 by design (a report, not a gate), so this only guards a crash. Gating would mean asserting | |
| # drift="0", and the residual 63 will not go to zero by any rule change — they are genuine stale | |
| # file:line maps in undated DESIGN studies, plus two audits that are manifestly artifacts-of-a-date | |
| # to a human but never write that date where a machine can read it. The lane refuses to guess genre | |
| # (measured: a bare date in the opening prose mis-dates three LIVE documents here), and git history | |
| # cannot help either — 90 of 98 stale anchors were CORRECT at their own doc's last commit, records | |
| # and rot alike. So the honest options are a zero-gate that is red on day one, or a ratchet ceiling | |
| # that goes red whenever a doc is edited in a shared tree. Neither earns its noise. | |
| # | |
| # What DOES gate is test/docdriftcheck.sh, which pins every lane and every rec= kind on a labelled | |
| # fixture, including the negative controls. Re-open this decision when the DESIGN docs' anchors are | |
| # fixed and the review notes carry a self-date: at that point drift= can be gated at 0. | |
| - name: Self-report (non-gating) — --doc-drift on our own docs | |
| run: ./asan/ripwire . --doc-drift --with-history || true | |
| # Sanitizer reports written via log_path survive gates that swallow stderr; keep them when a | |
| # step failed so the log tells us WHICH abort happened, not just that one did. | |
| - name: Upload sanitizer reports on failure | |
| if: failure() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: sanitizer-reports-${{ matrix.os }} | |
| path: /tmp/asanlog* | |
| if-no-files-found: ignore |