name: Core Smoke on: pull_request: types: [opened, synchronize, reopened, ready_for_review] push: branches: [master, develop] workflow_dispatch: permissions: contents: read concurrency: group: core-smoke-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true jobs: tools: name: Regression tools runs-on: ubuntu-latest timeout-minutes: 5 steps: - uses: actions/checkout@v4 with: persist-credentials: false - uses: actions/setup-python@v5 with: python-version: '3.12' - run: python -m pip install PyYAML==6.0.3 - name: Check regression tools run: timeout 60s python -m unittest discover -s tools -p 'test_*.py' -v smoke: name: Core smoke (60s) runs-on: ubuntu-latest timeout-minutes: 15 steps: - uses: actions/checkout@v4 with: persist-credentials: false - uses: actions/setup-python@v5 with: python-version: '3.12' - uses: astral-sh/setup-uv@v4 with: enable-cache: true - name: Install locked dependencies run: uv sync --dev --frozen - name: Read official plugin source pin id: plugins run: | python - <<'PY' import json, os from pathlib import Path revision = json.loads(Path('tests/fixtures/legacy_plugin_identity_sources.json').read_text())['revision'] with open(os.environ['GITHUB_OUTPUT'], 'a') as stream: stream.write(f'revision={revision}\n') PY - uses: actions/checkout@v4 with: repository: langbot-app/langbot-plugins ref: ${{ steps.plugins.outputs.revision }} path: .smoke-plugins persist-credentials: false - name: Prepare CLI and sandbox image env: GH_TOKEN: ${{ github.token }} run: | mkdir -p .smoke-bin gh release download v0.3.0 --repo langbot-app/langbot-cli --dir .smoke-bin \ --pattern lbctl_linux_amd64 --pattern checksums.txt cd .smoke-bin sha256sum --check --ignore-missing checksums.txt chmod +x lbctl_linux_amd64 docker pull python:3.12-alpine - name: Prepare real services and run the 60-second smoke run: | uv run --no-sync python tools/regression_ci.py \ --lbctl .smoke-bin/lbctl_linux_amd64 --plugins .smoke-plugins --output .qa/core-smoke - name: Publish gate result if: always() run: | python - <<'PY' import json, os from pathlib import Path path = Path('.qa/core-smoke/gate/gate.json') with open(os.environ['GITHUB_STEP_SUMMARY'], 'a') as out: if path.exists(): data = json.loads(path.read_text()) out.write(f"Core smoke: {'PASS' if data['passed'] else 'FAIL'}; " f"{data.get('elapsed_seconds', 0):.2f}/60 seconds; {data.get('requests', 0)} requests.\n") if data.get('infrastructure_error'): out.write('Preparation or teardown failed; inspect service logs.\n') out.write('Service/dependency preparation is outside the 60-second execution budget.\n') else: out.write('Core smoke did not produce a verdict; inspect preparation failure.\n') PY - name: Preserve verdicts and resource samples if: always() uses: actions/upload-artifact@v4 with: name: core-smoke-${{ github.run_id }} path: | .qa/core-smoke/gate/gate.json .qa/core-smoke/gate/resources.jsonl .qa/core-smoke/gate/suite.log .qa/core-smoke/gate/suite/cases.json .qa/core-smoke/gate/suite/summary.json .qa/core-smoke/gate/suite/health.json .qa/core-smoke/gate/suite/cleanup-checks.json .qa/core-smoke/*.log if-no-files-found: warn retention-days: 7