docs: fix accuracy drift found in documentation audit #56
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: | |
| merge_group: | |
| workflow_dispatch: | |
| jobs: | |
| # F-22 package isolation (fork-safe, no Unity license): prove every shipped package.json is a | |
| # self-consistent UPM graph - all five packages versioned in lockstep and every internal | |
| # com.neoxider.* dependency pin matches the pinned package's actual version. Catches a half-done | |
| # version bump (one of A-01/A-02's failure classes) before it reaches a consumer. | |
| package-graph: | |
| name: Package graph (lockstep + deps) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Verify package versions are in lockstep and internal deps resolve | |
| shell: bash | |
| run: | | |
| python3 - <<'PY' | |
| import json, glob, sys | |
| pkgs = {} | |
| for path in glob.glob('Assets/*/package.json'): | |
| with open(path, encoding='utf-8') as f: | |
| data = json.load(f) | |
| pkgs[data['name']] = (data.get('version', ''), data.get('dependencies', {})) | |
| versions = {v[0] for v in pkgs.values()} | |
| ok = True | |
| if len(versions) != 1: | |
| print(f"::error::package versions are not in lockstep: {versions}") | |
| ok = False | |
| for name, (ver, deps) in pkgs.items(): | |
| for dep, dver in deps.items(): | |
| if dep.startswith('com.neoxider.') and dep in pkgs and dver != pkgs[dep][0]: | |
| print(f"::error::{name} pins {dep}@{dver} but that package is {pkgs[dep][0]}") | |
| ok = False | |
| print("packages:", {n: v[0] for n, v in pkgs.items()}) | |
| sys.exit(0 if ok else 1) | |
| PY | |
| # F-12 trusted merge-queue gate: on the merge queue the repository secrets ARE available, so a | |
| # missing UNITY_LICENSE is a misconfiguration, not a forked PR - FAIL loudly instead of skipping, | |
| # so an unlicensed runner can never merge an uncompiled/untested wave (the A-01/A-02 class). | |
| merge-queue-gate: | |
| name: Merge-queue license gate | |
| runs-on: ubuntu-latest | |
| if: github.event_name == 'merge_group' | |
| steps: | |
| - name: Require UNITY_LICENSE on the merge queue | |
| shell: bash | |
| run: | | |
| if [ -z "${{ secrets.UNITY_LICENSE }}" ]; then | |
| echo "::error::UNITY_LICENSE is unavailable on the merge queue; the EditMode/PlayMode Unity" | |
| echo "::error::jobs would skip and let an uncompiled change merge. Configure the secret." | |
| exit 1 | |
| fi | |
| echo "UNITY_LICENSE present - Unity jobs will run and gate this merge." | |
| # Secret-free analyzer guard. Uses plain dotnet (no Unity license), so it also runs | |
| # on forked PRs where the Unity jobs are skipped. Building + testing the Roslyn | |
| # analyzer project guards the committed CoreAI.UnityAsyncAnalyzers.dll against drift | |
| # and proves rule CAIU001 (ban ConfigureAwait(false) in CoreAiUnity code) still fires. | |
| analyzer: | |
| name: Roslyn analyzer (dotnet) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-dotnet@v4 | |
| with: | |
| dotnet-version: '8.0.x' | |
| - name: Build analyzer + tests | |
| run: dotnet build tools/CoreAI.Tools.sln -c Release --nologo | |
| - name: Run analyzer tests (verifies CAIU001 fires) | |
| run: dotnet test tools/CoreAI.Tools.sln -c Release --no-build --nologo | |
| editmode-tests: | |
| name: EditMode (${{ matrix.name }}) | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| # Explicit include list (no cross-product). Three configurations must stay green: | |
| # lua - default project with the bundled Lua-CSharp runtime. | |
| # no-lua - COREAI_NO_LUA build so every Lua feature compiles out. | |
| # no-llm - COREAI_NO_LLM build so the layer under #if !COREAI_NO_LLM compiles out. | |
| include: | |
| - name: lua | |
| lua: lua | |
| no_llm: false | |
| - name: no-lua | |
| lua: no-lua | |
| no_llm: false | |
| - name: no-llm | |
| lua: lua | |
| no_llm: true | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Check UNITY_LICENSE availability for this run | |
| id: unity-license-check | |
| shell: bash | |
| run: | | |
| if [ -z "${{ secrets.UNITY_LICENSE }}" ]; then | |
| echo "::notice::Skipping EditMode tests because UNITY_LICENSE is unavailable (forked PRs do not receive repository secrets)." | |
| echo "run_tests=false" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "run_tests=true" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Define COREAI_NO_LUA | |
| if: matrix.lua == 'no-lua' | |
| run: | | |
| # The Lua-CSharp runtime ships bundled inside the CoreAI Mods package (Lua.dll), so there | |
| # is no package to remove — this job proves the project compiles with every Lua feature | |
| # compiled out via the COREAI_NO_LUA define (documented opt-out in | |
| # Assets/CoreAI/Docs/LUA_SANDBOX_SECURITY.md). | |
| # Append the define to every per-platform scriptingDefineSymbols entry. | |
| sed -i 's/: DOTWEEN$/: DOTWEEN;COREAI_NO_LUA/' ProjectSettings/ProjectSettings.asset | |
| grep -q 'COREAI_NO_LUA' ProjectSettings/ProjectSettings.asset || { echo 'COREAI_NO_LUA injection failed'; exit 1; } | |
| - name: Define COREAI_NO_LLM | |
| if: matrix.no_llm | |
| run: | | |
| # Compile out the LLM layer (real code lives under #if !COREAI_NO_LLM) so the | |
| # no-LLM build can't silently break. Mirrors the COREAI_NO_LUA injection above. | |
| sed -i 's/: DOTWEEN$/: DOTWEEN;COREAI_NO_LLM/' ProjectSettings/ProjectSettings.asset | |
| grep -q 'COREAI_NO_LLM' ProjectSettings/ProjectSettings.asset || { echo 'COREAI_NO_LLM injection failed'; exit 1; } | |
| - uses: actions/cache@v4 | |
| with: | |
| path: Library | |
| key: library-${{ matrix.name }}-${{ hashFiles('Packages/packages-lock.json', 'ProjectSettings/ProjectVersion.txt') }} | |
| restore-keys: | | |
| library-${{ matrix.name }}- | |
| - name: Run EditMode tests | |
| if: steps.unity-license-check.outputs.run_tests == 'true' | |
| uses: game-ci/unity-test-runner@v4 | |
| env: | |
| UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }} | |
| UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }} | |
| UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }} | |
| with: | |
| testMode: editmode | |
| artifactsPath: artifacts-${{ matrix.name }} | |
| checkName: EditMode results (${{ matrix.name }}) | |
| githubToken: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Enforce Lua sandbox escape-test coverage | |
| if: matrix.lua == 'lua' && matrix.no_llm == false && steps.unity-license-check.outputs.run_tests == 'true' | |
| run: | | |
| # The sandbox isolation suite must actually have run — a green build with | |
| # 0 executed escape tests (filter typo, asmdef regression, define drift) | |
| # must fail, not pass silently. | |
| count=$(grep -o 'LuaCsSecureSandboxEditModeTests' artifacts-${{ matrix.name }}/editmode-results.xml | wc -l) | |
| echo "LuaCsSecureSandboxEditModeTests occurrences in results: $count" | |
| if [ "$count" -lt 3 ]; then | |
| echo "::error::Lua sandbox escape tests did not run (expected the LuaCsSecureSandboxEditModeTests fixture in EditMode results)." | |
| exit 1 | |
| fi | |
| - uses: actions/upload-artifact@v4 | |
| if: always() && steps.unity-license-check.outputs.run_tests == 'true' | |
| with: | |
| name: test-results-${{ matrix.name }} | |
| path: artifacts-${{ matrix.name }} | |
| # Deterministic, no-LLM PlayMode suite (main-thread marshaling, streaming/non-streaming | |
| # parity, stop/cancel). Filtered to the FastNoLlm test assembly so the live LLM | |
| # benchmark / LlmVerification fixtures (which need a model server) never run here. | |
| playmode-fastnollm: | |
| name: PlayMode (FastNoLlm) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Check UNITY_LICENSE availability for this run | |
| id: unity-license-check | |
| shell: bash | |
| run: | | |
| if [ -z "${{ secrets.UNITY_LICENSE }}" ]; then | |
| echo "::notice::Skipping PlayMode tests because UNITY_LICENSE is unavailable (forked PRs do not receive repository secrets)." | |
| echo "run_tests=false" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "run_tests=true" >> "$GITHUB_OUTPUT" | |
| fi | |
| - uses: actions/cache@v4 | |
| with: | |
| path: Library | |
| key: library-playmode-${{ hashFiles('Packages/packages-lock.json', 'ProjectSettings/ProjectVersion.txt') }} | |
| restore-keys: | | |
| library-playmode- | |
| - name: Run PlayMode tests (FastNoLlm assembly only) | |
| if: steps.unity-license-check.outputs.run_tests == 'true' | |
| uses: game-ci/unity-test-runner@v4 | |
| env: | |
| UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }} | |
| UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }} | |
| UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }} | |
| with: | |
| testMode: playmode | |
| # The deterministic suite has no NUnit [Category]; isolate it by assembly name. | |
| # The LlmVerification / benchmark fixtures live in separate assemblies and are | |
| # excluded by not naming them here. | |
| customParameters: -assemblyNames "CoreAI.Tests.PlayMode.FastNoLlm" | |
| artifactsPath: artifacts-playmode | |
| checkName: PlayMode results (FastNoLlm) | |
| githubToken: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Enforce FastNoLlm PlayMode suite actually ran | |
| if: steps.unity-license-check.outputs.run_tests == 'true' | |
| run: | | |
| # A green build with 0 executed FastNoLlm tests (filter typo, asmdef rename, | |
| # define drift) must fail, not pass silently. | |
| count=$(grep -o 'PlayModeTests' artifacts-playmode/playmode-results.xml | wc -l) | |
| echo "PlayMode FastNoLlm test-case occurrences in results: $count" | |
| if [ "$count" -lt 10 ]; then | |
| echo "::error::FastNoLlm PlayMode suite did not run (expected the CoreAI.Tests.PlayMode.FastNoLlm fixtures in PlayMode results)." | |
| exit 1 | |
| fi | |
| - uses: actions/upload-artifact@v4 | |
| if: always() && steps.unity-license-check.outputs.run_tests == 'true' | |
| with: | |
| name: test-results-playmode | |
| path: artifacts-playmode |