check_mcp_refs: ide is supplied by the editor, not by a config file
#10
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 | |
| on: | |
| push: | |
| pull_request: | |
| jobs: | |
| contracts: | |
| runs-on: ubuntu-latest | |
| strategy: | |
| matrix: | |
| python-version: ["3.10", "3.12", "3.13"] | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: ${{ matrix.python-version }} | |
| # Every registered check must prove it can fail. This also runs each | |
| # tool's own --selftest, so the negative controls are exercised on CI | |
| # rather than only on the author's machine. | |
| - name: selftest contract | |
| run: python3 selftest_gate.py | |
| # The README declares its own load-bearing claims; verify each anchor | |
| # still exists in the text and the budget is respected. | |
| - name: README doc contract | |
| run: python3 doc_contract.py --document README.md | |
| # A gate that has never been observed to fail is the thing this repo is | |
| # about. Prove the two exit-1 paths on CI, not just in the selftests. | |
| - name: prove doc_contract rejects a violation | |
| run: | | |
| cat > /tmp/bad.md <<'MD' | |
| # x | |
| <!-- load-bearing:begin --> | |
| ## L | |
| | # | a | b | | |
| |---|---|---| | |
| | 1 | `absent from body` | boom | | |
| <!-- load-bearing:end --> | |
| body | |
| MD | |
| sed -i 's/^ //' /tmp/bad.md | |
| # Nonzero is not the claim. Measured: a SyntaxError in the tool exits | |
| # 1 and a typo'd flag exits 2 -- both used to print "rejected as | |
| # expected" here, which is the shape of failure this repository is | |
| # about. | |
| set +e | |
| out=$(python3 doc_contract.py --document /tmp/bad.md 2>&1); rc=$? | |
| set -e | |
| printf '%s\n' "$out" | |
| if [ "$rc" -eq 0 ]; then | |
| echo "::error::doc_contract accepted a missing anchor"; exit 1 | |
| fi | |
| if [ "$rc" -ne 1 ]; then | |
| echo "::error::doc_contract exited $rc, not 1 -- a crash or a usage error is not a rejection"; exit 1 | |
| fi | |
| printf '%s\n' "$out" | grep -Fq "anchor not present in document: 'absent from body'" || { | |
| echo "::error::doc_contract exited 1, but not on the missing-anchor rule this step exists to prove"; exit 1; } | |
| echo "rejected as expected, by the missing-anchor rule" | |
| - name: prove selftest_gate rejects a silent check | |
| run: | | |
| mkdir -p /tmp/g && printf 'import sys\nif "--selftest" in sys.argv: sys.exit(0)\n' > /tmp/g/silent.py | |
| # `documented_samples` is required by load_registry, so the fixture | |
| # declares it. Without it this registry is rejected at LOAD time and | |
| # never reaches the rule this step exists to prove -- which is exactly | |
| # what happened on 2026-08-02 and is why the grep below is on the rule | |
| # rather than on the exit code. | |
| printf '{"checks":[{"id":"silent","path":"silent.py","min_controls":1}],"documented_samples":["silent.py"]}\n' > /tmp/g/registry.json | |
| set +e | |
| out=$(python3 selftest_gate.py --registry /tmp/g/registry.json 2>&1); rc=$? | |
| set -e | |
| printf '%s\n' "$out" | |
| if [ "$rc" -eq 0 ]; then | |
| echo "::error::selftest_gate accepted a check that reports no controls"; exit 1 | |
| fi | |
| if [ "$rc" -ne 1 ]; then | |
| echo "::error::selftest_gate exited $rc, not 1 -- a crash or a usage error is not a rejection"; exit 1 | |
| fi | |
| # Names the check AND the rule. The rule that actually fires on this | |
| # fixture is the missing-summary one, not min_controls, which is what | |
| # the old message claimed. | |
| printf '%s\n' "$out" | grep -Fq '[FAIL] silent: selftest output has no `passed=N failed=M` line' || { | |
| echo "::error::selftest_gate exited 1 for a reason other than the silent check's missing summary"; exit 1; } | |
| echo "rejected as expected, on the missing-summary rule, naming the check" |