diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0b36ce97..6e51dc83 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,3 +30,45 @@ jobs: exit $failed - name: Run unit tests run: python -m pytest -q + + gnn-conformance: + # Validates gnn/gaussian_agent.gnn.md with the upstream parser revision + # pinned in gnn/upstream.json. The pinned revision is fetched from the + # public repository at the recorded SHA into its own runtime; the strict + # gate never skips, and tests/test_gnn_conformance.py fails (never skips) + # when the explicitly selected GNN_ROOT checkout is missing or unusable. + name: gnn conformance (pinned v5.0.3) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0 + with: + python-version: "3.12" + - name: Install core deps + run: | + python -m pip install --upgrade pip + pip install -e ".[dev]" + - name: Provision the pinned GNN parser runtime + env: + GNN_ROOT: ${{ runner.temp }}/gnn + run: | + REVISION=$(python -c "import json; print(json.load(open('gnn/upstream.json'))['commit'])") + TAG=$(python -c "import json; print(json.load(open('gnn/upstream.json'))['tag'])") + git init --quiet "$GNN_ROOT" + git -C "$GNN_ROOT" remote add origin \ + https://github.com/ActiveInferenceInstitute/Generalized_Notation_Notation.git + # A pin that no longer resolves upstream fails here rather than skipping. + git -C "$GNN_ROOT" fetch --depth 1 origin "$REVISION" "refs/tags/$TAG:refs/tags/$TAG" + git -C "$GNN_ROOT" checkout --quiet --detach "$REVISION" + python -m venv "$GNN_ROOT/.venv" + # The pinned extractor needs numpy and defusedxml; a missing dependency + # fails the run loudly instead of degrading to a skip. + "$GNN_ROOT/.venv/bin/python" -m pip install --quiet numpy defusedxml + - name: Validate the GNN specification against the pinned parser + env: + GNN_ROOT: ${{ runner.temp }}/gnn + run: python scripts/validate_gnn_spec.py + - name: Run the pinned-upstream conformance tests + env: + GNN_ROOT: ${{ runner.temp }}/gnn + run: python -m pytest tests/test_gnn_conformance.py -q diff --git a/AGENTS.md b/AGENTS.md index 00323e6c..ab2d46b1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -29,7 +29,10 @@ Multi-language Active Inference framework for advanced AI research and applicati - Ensure cross-language implementation parity. - Keep `gnn/gaussian_agent.gnn.md` in step with `active_infer_ants/agent.py`; validate it with `python scripts/validate_gnn_spec.py` (needs the GNN checkout pinned in - `gnn/upstream.json`) or `python -m pytest tests/test_gnn_conformance.py`. GNN's + `gnn/upstream.json`) or `python -m pytest tests/test_gnn_conformance.py`. CI runs both in + the `gnn conformance (pinned v5.0.3)` job, which fetches the pinned revision from the + public upstream repository and provisions its interpreter, so the pin is re-checked on + every push and pull request. GNN's [downstream adoption guide](https://github.com/ActiveInferenceInstitute/Generalized_Notation_Notation/blob/main/docs/gnn/integration/downstream_adoption.md) documents the adoption patterns, pin discipline, and drift-guard checks used here. diff --git a/tests/test_gnn_conformance.py b/tests/test_gnn_conformance.py index ff3a10b2..a7d754f1 100644 --- a/tests/test_gnn_conformance.py +++ b/tests/test_gnn_conformance.py @@ -3,11 +3,14 @@ The specification is validated with the pinned GNN revision when the GNN checkout and a suitable interpreter are available; the structural and specification-vs-agent checks always run so CI still guards the specification. +When ``GNN_ROOT`` selects a checkout explicitly (as CI does after provisioning +the pinned revision), an unusable checkout is a failure, never a skip. """ from __future__ import annotations import copy +import os import re import sys from pathlib import Path @@ -53,6 +56,8 @@ def extracted_model() -> dict: try: return extract_spec() except GnnUnavailable as exc: + if os.environ.get("GNN_ROOT"): + pytest.fail(f"GNN_ROOT selects a checkout, but the pinned parser is unavailable: {exc}") pytest.skip(f"pinned GNN checkout unavailable: {exc}")