Repository navigation
ci: add adversarial corpus manifest checks #106
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: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| jobs: | |
| test: | |
| runs-on: ubuntu-latest | |
| strategy: | |
| matrix: | |
| python-version: ["3.10", "3.11", "3.12"] | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Set up Python ${{ matrix.python-version }} | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: ${{ matrix.python-version }} | |
| - name: Install dependencies | |
| run: pip install pytest jsonschema | |
| - name: Drift alarm - no StopMachine class in primitives | |
| run: | | |
| if grep -rn "class StopMachine" primitives/; then | |
| echo "DRIFT DETECTED: StopMachine class found in primitives/" | |
| exit 1 | |
| fi | |
| - name: Drift alarm - no Gate implementation in primitives | |
| run: | | |
| if grep -rn "class.*Gate" primitives/authority-gate-v0/ primitives/stop-machine-v0/; then | |
| echo "DRIFT DETECTED: Gate class found in v0 folders" | |
| exit 1 | |
| fi | |
| - name: Drift alarm - no runtime imports from analysis/docs/artifacts/examples | |
| run: | | |
| echo "Checking runtime roots for forbidden imports..." | |
| FOUND=0 | |
| for token in "import analysis" "from analysis" "import docs" "from docs" "import artifacts" "from artifacts" "import examples" "from examples"; do | |
| if grep -rn "$token" stop_machine.py primitives/ --include="*.py" 2>/dev/null; then | |
| echo "DRIFT DETECTED: forbidden token '$token' found in runtime code" | |
| FOUND=1 | |
| fi | |
| done | |
| if [ "$FOUND" -eq 1 ]; then | |
| exit 1 | |
| fi | |
| echo "OK: no forbidden imports found." | |
| - name: Run primitive tests | |
| run: python -m pytest primitives -v | |
| - name: Run root tests | |
| run: python -m pytest test_stop_machine.py -v | |
| - name: Run invariant tests | |
| run: python -m pytest tests/ -v | |
| - name: Run adversarial surface gate fixture harness | |
| run: python -m pytest test_adversarial_surface_fixtures.py -v | |
| - name: Run surface-to-spine integration tests | |
| run: python -m pytest test_surface_to_spine_integration.py -v | |
| - name: Validate fixture schema | |
| run: | | |
| cat > /tmp/validate_fixtures.py << 'PYEOF' | |
| import json, glob, sys | |
| try: | |
| import jsonschema | |
| except ImportError: | |
| print("jsonschema not installed — skipping schema validation") | |
| sys.exit(0) | |
| schema_path = "schemas/adversarial_surface_fixture.schema.json" | |
| fixture_glob = "tests/fixtures/adversarial_surface_gate/*.json" | |
| with open(schema_path) as f: | |
| schema = json.load(f) | |
| files = sorted(glob.glob(fixture_glob)) | |
| if not files: | |
| print("No fixtures found — nothing to validate") | |
| sys.exit(0) | |
| failures = [] | |
| for path in files: | |
| with open(path) as f: | |
| fixture = json.load(f) | |
| try: | |
| jsonschema.validate(instance=fixture, schema=schema) | |
| except jsonschema.ValidationError as e: | |
| print(f"SCHEMA FAIL: {path}: {e.message}") | |
| failures.append(path) | |
| if failures: | |
| print(f"{len(failures)} fixture(s) failed schema validation") | |
| sys.exit(1) | |
| print(f"All {len(files)} fixture(s) valid against schema.") | |
| PYEOF | |
| python3 /tmp/validate_fixtures.py | |
| - name: Generate verification receipt | |
| run: python scripts/verify_adversarial_surface_gate.py | |
| - name: Generate adversarial corpus manifest | |
| run: python scripts/generate_adversarial_corpus_manifest.py | |
| - name: Run adversarial corpus manifest checks | |
| run: python -m pytest test_adversarial_corpus_manifest.py -v | |
| - name: Upload verification receipt | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: surface-gate-receipt-${{ matrix.python-version }}-${{ github.run_id }} | |
| path: tests/adversarial/latest_surface_gate_verification_receipt.json | |
| if-no-files-found: warn | |
| - name: Upload adversarial corpus manifest | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: adversarial-corpus-manifest-${{ matrix.python-version }}-${{ github.run_id }} | |
| path: tests/adversarial/latest_adversarial_corpus_manifest.json | |
| if-no-files-found: warn | |
| geometry-analysis: | |
| needs: [test] | |
| if: always() | |
| continue-on-error: true | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Set up Python 3.12 | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.12" | |
| - name: Validate geometry JSONL artefacts | |
| shell: bash | |
| run: | | |
| cat > /tmp/validate_geometry.py << 'PYEOF' | |
| import json, glob, sys | |
| files = sorted(glob.glob("artifacts/geometry/**/*.jsonl", recursive=True)) | |
| if not files: | |
| print("No geometry artefacts found. Skipping validation.") | |
| sys.exit(0) | |
| REQUIRED_KEYS = {"schema_version", "primitive", "event", "exit", "violations", "input_hash", "result_hash"} | |
| VALID_EXITS = {"ALLOW", "HOLD", "DENY", "SILENCE"} | |
| file_count = len(files) | |
| line_count = 0 | |
| parse_fail = 0 | |
| exits = {} | |
| for path in files: | |
| with open(path, "r", encoding="utf-8") as fh: | |
| for lineno, raw in enumerate(fh, 1): | |
| raw = raw.strip() | |
| if not raw: | |
| continue | |
| line_count += 1 | |
| try: | |
| obj = json.loads(raw) | |
| except json.JSONDecodeError as e: | |
| print(f"PARSE FAIL: {path}:{lineno}: {e}") | |
| parse_fail += 1 | |
| continue | |
| missing = REQUIRED_KEYS - set(obj.keys()) | |
| if missing: | |
| print(f"MISSING KEYS: {path}:{lineno}: {missing}") | |
| parse_fail += 1 | |
| continue | |
| if obj.get("schema_version") != "0.1": | |
| print(f"BAD SCHEMA: {path}:{lineno}: {obj.get('schema_version')}") | |
| parse_fail += 1 | |
| ex = obj.get("exit", "") | |
| if ex not in VALID_EXITS: | |
| print(f"BAD EXIT: {path}:{lineno}: {ex}") | |
| parse_fail += 1 | |
| exits[ex] = exits.get(ex, 0) + 1 | |
| print("") | |
| print("=== Geometry JSONL Summary ===") | |
| print(f"Files: {file_count}") | |
| print(f"Lines: {line_count}") | |
| print(f"Parse fails: {parse_fail}") | |
| for ex in sorted(exits): | |
| print(f" {ex}: {exits[ex]}") | |
| if parse_fail: | |
| print(f"WARNING: {parse_fail} validation issue(s) found.") | |
| sys.exit(1) | |
| print("All lines valid.") | |
| PYEOF | |
| python3 /tmp/validate_geometry.py | |
| - name: Upload geometry artefacts | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: geometry-artifacts-${{ github.run_id }} | |
| path: artifacts/geometry | |
| if-no-files-found: ignore |