diff --git a/.coderabbit.yaml b/.coderabbit.yaml new file mode 100644 index 00000000..a8e81637 --- /dev/null +++ b/.coderabbit.yaml @@ -0,0 +1,32 @@ +# SPDX-License-Identifier: MPL-2.0 +# Review-only. No AI-authored finishing-touch commits. +# Canonical language policy: +# https://github.com/metadatastician/standards/blob/main/3-practice/LANGUAGE-POLICY.adoc +reviews: + auto_review: + enabled: true + drafts: false + path_instructions: + - path: "**/*" + instructions: >- + Estate policy prohibits NEW Python files, scripts, test code, + dependencies, and Python package manifests with no exceptions. + Do not recommend Python or generate Python fixes. The existing + upstream React and TypeScript frontend is temporarily permitted until + Marid is ready. Scientific code is Julia and R with pinned toolchains. + Flag additions of Python as a policy violation. Do not rewrite + unrelated upstream code for cosmetic compliance. Validate any + replacement through scientific result parity and tests. + finishing_touches: + docstrings: + enabled: false + unit_tests: + enabled: false + simplify: + enabled: false + autofix: + enabled: false + fix_ci: + enabled: false + resolve_merge_conflict: + enabled: false diff --git a/.github/workflows/language-policy.yml b/.github/workflows/language-policy.yml new file mode 100644 index 00000000..3b3effe6 --- /dev/null +++ b/.github/workflows/language-policy.yml @@ -0,0 +1,43 @@ +# SPDX-License-Identifier: MPL-2.0 +name: Language policy (no new Python) +on: + pull_request: + branches: [main] + push: + branches: [main] + workflow_dispatch: +permissions: + contents: read +jobs: + no-new-python: + name: No new Python + runs-on: ubuntu-24.04 + timeout-minutes: 5 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + fetch-depth: 0 + ref: ${{ github.event.pull_request.head.sha || github.sha }} + - name: Refuse Python additions and edits (allow deletion) + env: + PR_BASE_SHA: ${{ github.event.pull_request.base.sha }} + PUSH_BEFORE_SHA: ${{ github.event.before }} + EVENT_KIND: ${{ github.event_name }} + run: | + set -euo pipefail + if [[ "$EVENT_KIND" == pull_request ]]; then + base=$PR_BASE_SHA + elif [[ "$EVENT_KIND" == push ]]; then + base=$PUSH_BEFORE_SHA + if [[ "$base" == 0000000000000000000000000000000000000000 ]]; then + echo 'No push base available: refusing ambiguous evidence' >&2 + exit 2 + fi + else + echo 'Manual probe covers HEAD^..HEAD, not PR merge-base scope' + base=$(git rev-parse HEAD^) + fi + bash scripts/check-no-new-python.sh "$base" + - name: Planted-negative and positive controls + run: bash scripts/tests/test-no-new-python.sh diff --git a/scripts/check-no-new-python.sh b/scripts/check-no-new-python.sh new file mode 100644 index 00000000..65d32107 --- /dev/null +++ b/scripts/check-no-new-python.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# Refuse newly added/modified Python source and manifests. Untouched inherited +# debt is not a blanket authorisation to add more. Deletion is allowed. +set -euo pipefail +if [[ $# -ne 1 || ! "$1" =~ ^[0-9a-fA-F]{40}$ ]]; then + printf 'Usage: %s \n' "$0" >&2 + exit 2 +fi +base=$1 +if ! git cat-file -e "${base}^{commit}"; then + echo 'Base commit unavailable; no language-policy evidence' >&2 + exit 2 +fi +changed=$(mktemp) +trap 'rm -f "$changed"' EXIT +if ! git diff --name-only --diff-filter=ACMR -z "${base}...HEAD" -- > "$changed"; then + echo 'Cannot determine changed files; refusing a false green' >&2 + exit 2 +fi +bad=0 +while IFS= read -r -d '' file; do + case "${file##*/}" in + *.py|*.pyi|pyproject.toml|Pipfile|Pipfile.lock|poetry.lock|uv.lock|.python-version|setup.cfg|tox.ini|pytest.ini|requirements*.txt|requirements*.in) + printf 'BANNED Python contribution: %q\n' "$file" >&2 + bad=1 ;; + esac +done < "$changed" +if (( bad )); then + echo 'Python is banned for new work; migrate to approved languages. Removal-only cleanup is allowed.' >&2 + echo 'Canon: metadatastician/standards/3-practice/LANGUAGE-POLICY.adoc section 3' >&2 + exit 1 +fi +echo 'Language policy PASS: no new or modified Python files/manifests in this diff; legacy untouched files are not certified.' diff --git a/scripts/tests/test-no-new-python.sh b/scripts/tests/test-no-new-python.sh new file mode 100644 index 00000000..db733e1c --- /dev/null +++ b/scripts/tests/test-no-new-python.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# Both pass and planted-fail controls for the diff-based ban. +set -euo pipefail +script=$(cd "$(dirname "$0")/.." && pwd)/check-no-new-python.sh +scratch=$(mktemp -d) +trap 'rm -rf "$scratch"' EXIT +cd "$scratch" +git init -q +git config user.name 'Language Gate Test' +git config user.email 'language-gate@example.invalid' +mkdir -p bench scripts +printf 'print("legacy")\n' > scripts/legacy.py +git add . && git commit -qm 'Pre-existing Python debt' +base=$(git rev-parse HEAD) +expect_failure() { + if bash "$script" "$base" > "$scratch/output.log" 2>&1; then + echo 'ERROR: planted Python violation passed' >&2; exit 1 + fi + grep -q 'BANNED Python contribution:' "$scratch/output.log" || { cat "$scratch/output.log" >&2; exit 1; } +} +expect_pass() { bash "$script" "$base" > /dev/null; } +printf 'cat("R allowed")\n' > bench/check.R +git add . && git commit -qm 'Add R' +expect_pass +printf 'print("new")\n' > bench/new.py +git add . && git commit -qm 'Add Python' +expect_failure +git rm -q bench/new.py && git commit -qm 'Remove Python' +expect_pass +printf 'print("changed")\n' >> scripts/legacy.py +git add . && git commit -qm 'Edit legacy Python' +expect_failure +git restore --source="$base" scripts/legacy.py +git add . && git commit -qm 'Restore legacy' +expect_pass +printf '[project]\nname = "wrong"\n' > pyproject.toml +git add . && git commit -qm 'Add Python manifest' +expect_failure +git rm -q pyproject.toml && git commit -qm 'Remove manifest' +expect_pass +git rm -q scripts/legacy.py && git commit -qm 'Retire Python' +expect_pass +if bash "$script" not-a-sha >/dev/null 2>&1; then echo 'Malformed base passed' >&2; exit 1; fi +if bash "$script" 0000000000000000000000000000000000000000 >/dev/null 2>&1; then echo 'Missing base passed' >&2; exit 1; fi +echo 'no-new-python gate: planted-positive and planted-negative fixtures PASS'