Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 21 additions & 7 deletions .github/workflows/ci-integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
# - ci.yml builds the PR's code in an unprivileged context and uploads binary artifacts
# - This workflow downloads those artifacts and runs tests from the DEFAULT BRANCH (main)
# - PR code is never checked out in a privileged context
# - Environment approval gate ensures a maintainer reviews the PR before tests run
# - Fork PRs require manual environment approval before tests run (security gate)
# - Internal PRs (same repo) skip the approval gate — contributors already have write access
name: Integration Tests (PR)

env:
Expand All @@ -21,19 +22,32 @@ permissions:
statuses: write

jobs:
# Single approval gate — maintainer reviews PR code, then approves this environment.
# All downstream jobs chain off this, so approval happens once.
approve:
# Fork PRs: require manual approval via environment protection rules.
# A maintainer must review the fork's code before secrets are exposed to its artifacts.
approve-fork:
if: >
github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.event == 'pull_request'
github.event.workflow_run.event == 'pull_request' &&
github.event.workflow_run.head_repository.full_name != github.repository
runs-on: ubuntu-latest
environment: integration-tests
steps:
- run: echo "Integration tests approved for ${{ github.event.workflow_run.head_branch }}"
- run: echo "Fork PR approved for ${{ github.event.workflow_run.head_branch }} from ${{ github.event.workflow_run.head_repository.full_name }}"

# Internal PRs: skip approval gate — contributors already have write access to the repo.
approve-internal:
if: >
github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.event == 'pull_request' &&
github.event.workflow_run.head_repository.full_name == github.repository
runs-on: ubuntu-latest
steps:
- run: echo "Internal PR auto-approved for ${{ github.event.workflow_run.head_branch }}"

smoke-test:
needs: [approve]
needs: [approve-fork, approve-internal]
# Run if either approval job succeeded (the other will be skipped)
if: always() && (needs.approve-fork.result == 'success' || needs.approve-internal.result == 'success')
runs-on: ${{ matrix.os }}
permissions:
contents: read
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ jobs:
uses: actions/checkout@v4

- name: Initialize CodeQL
uses: github/codeql-action/init@v3
uses: github/codeql-action/init@v4
with:
languages: ${{ matrix.language }}

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
uses: github/codeql-action/analyze@v4
13 changes: 8 additions & 5 deletions tests/integration/test_ado_e2e.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"""

import os
import shutil
import subprocess
import tempfile
from pathlib import Path
Expand All @@ -24,11 +25,13 @@

def run_apm_command(cmd: str, cwd: Path, timeout: int = 60) -> subprocess.CompletedProcess:
"""Run an APM CLI command and return the result."""
# Use the development version of APM
apm_path = Path(__file__).parent.parent.parent / ".venv" / "bin" / "apm"
if not apm_path.exists():
# Fallback to system apm
apm_path = "apm"
# Prefer binary on PATH (CI uses the PR artifact there)
apm_on_path = shutil.which("apm")
if apm_on_path:
apm_path = apm_on_path
else:
# Fallback to local dev venv
apm_path = Path(__file__).parent.parent.parent / ".venv" / "bin" / "apm"

full_cmd = f"{apm_path} {cmd}"
result = subprocess.run(
Expand Down
6 changes: 6 additions & 0 deletions tests/integration/test_mixed_deps.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"""

import os
import shutil
import subprocess
import pytest
from pathlib import Path
Expand Down Expand Up @@ -45,6 +46,11 @@ def temp_project(tmp_path):
@pytest.fixture
def apm_command():
"""Get the path to the APM CLI executable."""
# Prefer binary on PATH (CI uses the PR artifact there)
apm_on_path = shutil.which("apm")
if apm_on_path:
return apm_on_path
# Fallback to local dev venv
venv_apm = Path(__file__).parent.parent.parent / ".venv" / "bin" / "apm"
if venv_apm.exists():
return str(venv_apm)
Expand Down
6 changes: 6 additions & 0 deletions tests/integration/test_skill_compile.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"""

import os
import shutil
import subprocess
import pytest
from pathlib import Path
Expand Down Expand Up @@ -45,6 +46,11 @@ def temp_project(tmp_path):
@pytest.fixture
def apm_command():
"""Get the path to the APM CLI executable."""
# Prefer binary on PATH (CI uses the PR artifact there)
apm_on_path = shutil.which("apm")
if apm_on_path:
return apm_on_path
# Fallback to local dev venv
venv_apm = Path(__file__).parent.parent.parent / ".venv" / "bin" / "apm"
if venv_apm.exists():
return str(venv_apm)
Expand Down
8 changes: 6 additions & 2 deletions tests/integration/test_skill_install.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"""

import os
import shutil
import subprocess
import pytest
from pathlib import Path
Expand Down Expand Up @@ -45,11 +46,14 @@ def temp_project(tmp_path):
@pytest.fixture
def apm_command():
"""Get the path to the APM CLI executable."""
# Use the development version from source
# Prefer binary on PATH (CI uses the PR artifact there)
apm_on_path = shutil.which("apm")
if apm_on_path:
return apm_on_path
# Fallback to local dev venv
venv_apm = Path(__file__).parent.parent.parent / ".venv" / "bin" / "apm"
if venv_apm.exists():
return str(venv_apm)
# Fallback to system apm
return "apm"


Expand Down