mirror of
https://github.com/superdesigndev/treg.git
synced 2026-10-02 03:24:35 +08:00
Brings main's 86 commits (dashboard boot and loading, legacy dashboard removal, test pruning, overflow and routing fixes) together with the tool hub branch. Conflicts, both sides kept unless noted: - the legacy dashboard stays deleted, as on main; - App.vue and the dashboard state: main's search page and loading states plus the hub pages; - ci.yml: main's Postgres job, with the hub tests added to its list; - dev-local.sh: main's server environment plus the hub flag passthrough; - test_call_application_contract.py, test_marketplace_call.py: main's pruned files plus the hub branch's sync `settle: usage` test. Not conflicts: main and the hub branch fixed the same CompanyEnrich empty-page billing; main's rule runs first, so the hub branch's copy and its test are dropped. The Listing-tab test reads the Vue source instead of the deleted legacy page.
226 lines
9.5 KiB
YAML
226 lines
9.5 KiB
YAML
name: CI
|
|
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
pull_request:
|
|
|
|
# A new push supersedes the running build of the same branch or PR: the outdated run is cancelled
|
|
# instead of holding a runner and queueing the new one behind it (two overlapped runs were live
|
|
# the day this landed).
|
|
concurrency:
|
|
group: ci-${{ github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
# Least-privilege: CI only reads the repo (checkout + tests + secret scan).
|
|
permissions:
|
|
contents: read
|
|
|
|
jobs:
|
|
test:
|
|
runs-on: ubuntu-latest
|
|
# A throttled shared runner once turned the 5-minute suite into 20+ (log showed 68s before the
|
|
# first test started). Die loudly instead of eating half an hour; a re-run usually lands on a
|
|
# healthier machine.
|
|
timeout-minutes: 15
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 2 # the docs-only check diffs HEAD against its parent
|
|
- name: Detect a docs-only push
|
|
id: docs
|
|
# Direct pushes that change only docs/ and *.md skip the suite — half of one week's runs
|
|
# bought nothing. PULL REQUESTS ALWAYS RUN: the branch-protection check must report, and a
|
|
# skipped required check would block the merge forever.
|
|
run: |
|
|
ONLY=false
|
|
if [ "${{ github.event_name }}" = "push" ]; then
|
|
CHANGED=$(git diff --name-only HEAD^ HEAD || true)
|
|
if [ -n "$CHANGED" ] && ! echo "$CHANGED" | grep -qvE '^docs/|\.md$'; then
|
|
ONLY=true
|
|
fi
|
|
fi
|
|
echo "docs-only: $ONLY"
|
|
echo "only=$ONLY" >> "$GITHUB_OUTPUT"
|
|
- uses: astral-sh/setup-uv@v7
|
|
if: steps.docs.outputs.only != 'true'
|
|
with:
|
|
python-version: "3.13"
|
|
- uses: actions/setup-node@v4
|
|
if: steps.docs.outputs.only != 'true'
|
|
with:
|
|
node-version: '22'
|
|
cache: npm
|
|
cache-dependency-path: frontend/package-lock.json
|
|
- name: Build dashboard
|
|
if: steps.docs.outputs.only != 'true'
|
|
run: bash scripts/build-dashboard.sh
|
|
- name: Install dependencies
|
|
if: steps.docs.outputs.only != 'true'
|
|
run: uv sync --locked
|
|
- name: Enforce import boundaries
|
|
if: steps.docs.outputs.only != 'true'
|
|
run: uv run --locked lint-imports
|
|
- name: Run tests
|
|
if: steps.docs.outputs.only != 'true'
|
|
# -n auto: the suite is 2,700+ small independent tests — serial execution is what let a
|
|
# throttled runner turn minutes into tens of minutes. xdist arrives via --with rather than
|
|
# the lockfile on purpose (local daily default uses the same --with).
|
|
run: uv run --with pytest-xdist pytest -n auto -v -o faulthandler_timeout=300
|
|
|
|
test-postgres:
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 15
|
|
services:
|
|
postgres:
|
|
image: postgres:16
|
|
env:
|
|
POSTGRES_USER: treg
|
|
POSTGRES_PASSWORD: treg
|
|
POSTGRES_DB: treg_test
|
|
ports:
|
|
- 5432:5432
|
|
options: >-
|
|
--health-cmd "pg_isready -U treg -d treg_test"
|
|
--health-interval 10s
|
|
--health-timeout 5s
|
|
--health-retries 5
|
|
env:
|
|
TREG_TEST_DB_URL: postgresql+asyncpg://treg:treg@127.0.0.1:5432/treg_test
|
|
PYTHONUNBUFFERED: '1'
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: astral-sh/setup-uv@v7
|
|
with:
|
|
python-version: "3.13"
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: '22'
|
|
cache: npm
|
|
cache-dependency-path: frontend/package-lock.json
|
|
- name: Build dashboard
|
|
run: bash scripts/build-dashboard.sh
|
|
- name: Install dependencies
|
|
run: uv sync --locked
|
|
- name: Run Postgres tests
|
|
# Each xdist worker gets its own database (treg_test_gw0, ...; see tests/conftest.py), so
|
|
# reset_db() in one worker never empties another's tables.
|
|
# -v + faulthandler: this job has twice been killed at its time limit with zero output on
|
|
# this runner while the identical list passes locally. Per-test lines locate the stall;
|
|
# the fault handler dumps every thread's stack after 300s of one test.
|
|
run: >-
|
|
uv run --frozen --with pytest-xdist python -m pytest -n auto -v -o faulthandler_timeout=300
|
|
tests/callmatrix
|
|
tests/test_team_limit.py
|
|
tests/test_ledger.py
|
|
tests/test_ledger_claim_review.py
|
|
tests/test_asynctasks.py
|
|
tests/test_alembic_baseline.py
|
|
tests/test_enrich_arena.py
|
|
tests/test_arena_insights.py
|
|
tests/test_arena_verification_insights.py
|
|
tests/test_feedback.py
|
|
tests/test_archive.py
|
|
tests/test_cache_result_admission.py
|
|
tests/test_archive_r2.py
|
|
tests/test_call_cancellation.py
|
|
tests/test_call_pool_discipline.py
|
|
tests/test_api_keys.py
|
|
tests/test_postgres_reset.py
|
|
tests/test_read_replica.py
|
|
tests/test_marketplace_call.py
|
|
tests/test_capacity_overflow_spend.py
|
|
tests/test_billing.py
|
|
tests/test_agent_capture.py
|
|
tests/test_adsconv.py
|
|
tests/test_health.py
|
|
tests/test_localrun.py
|
|
tests/test_orgs.py
|
|
tests/test_orgs_isolation.py
|
|
tests/test_orgs_mgmt.py
|
|
tests/test_hub.py
|
|
tests/test_hub_sandbox.py
|
|
tests/test_mcp.py
|
|
- name: Dump Postgres activity on failure
|
|
# Three CI-only hangs at archive drains were undiagnosable from Python stacks alone; this
|
|
# shows the DATABASE's view: every session, its state (idle in transaction = a leaked or
|
|
# blocked one), what it waits on, and ungranted locks with their holders' queries.
|
|
# Delete once the hang is understood and fixed.
|
|
if: failure()
|
|
env:
|
|
PGPASSWORD: treg
|
|
run: |
|
|
psql -h 127.0.0.1 -U treg -d treg_test -x -c "select datname, pid, state, wait_event_type, wait_event, xact_start, state_change, left(query,140) as query from pg_stat_activity where datname like 'treg_test%' order by xact_start nulls last;" || true
|
|
psql -h 127.0.0.1 -U treg -d treg_test -c "select l.pid, l.locktype, l.mode, l.granted, a.state, left(a.query,90) as query from pg_locks l join pg_stat_activity a using(pid) where not l.granted;" || true
|
|
|
|
# RESTORED 2026-08-24. This job is a REQUIRED check on main, and the CI rewrite in 528383d
|
|
# dropped it while keeping the requirement — so for three weeks every pull request waited on a
|
|
# check that could never report, and only an admin bypass moved anything. It never skips (see
|
|
# the docs-only note in `test`: a skipped required check blocks a merge forever), and it scans
|
|
# the FULL history, because a secret removed from the tip still lives in the commit that added
|
|
# it. `.gitleaks.toml` allowlists the deliberately-fake placeholders in the tests and the demo
|
|
# sandbox; everything else is a real finding.
|
|
gitleaks:
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 10
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 0 # scan the full history of the push/PR, not just the tip
|
|
- name: Install gitleaks
|
|
env:
|
|
GITLEAKS_VERSION: "8.21.2"
|
|
run: |
|
|
curl -sSfL "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" \
|
|
| tar -xz gitleaks
|
|
sudo mv gitleaks /usr/local/bin/
|
|
- name: Scan for secrets
|
|
# HEAD is the PR merge commit (both parents) or the pushed main commit. Scan all
|
|
# reachable history, including deleted secrets, without unrelated fetched branch tips.
|
|
run: gitleaks detect --source . --config .gitleaks.toml --log-opts="HEAD" --verbose --redact
|
|
|
|
frontend:
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 15
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: astral-sh/setup-uv@v7
|
|
with:
|
|
python-version: "3.13"
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: '22'
|
|
cache: npm
|
|
cache-dependency-path: frontend/package-lock.json
|
|
- run: uv sync --locked
|
|
- run: bash scripts/build-dashboard.sh
|
|
- run: npm --prefix frontend test
|
|
- name: Install browser
|
|
working-directory: frontend
|
|
run: npx playwright install --with-deps chromium
|
|
- run: npm --prefix frontend run test:e2e
|
|
- name: Check distributable assets
|
|
run: |
|
|
uv build
|
|
python - <<'PYCODE'
|
|
from pathlib import Path
|
|
from tarfile import open as open_tar
|
|
from zipfile import ZipFile
|
|
import re
|
|
with ZipFile(next(Path('dist').glob('*.whl'))) as wheel:
|
|
assert not [name for name in wheel.namelist() if name.endswith('.mp4')]
|
|
html = wheel.read('treg/web/dashboard/index.html').decode()
|
|
assets = re.findall(r'/app/ui/(assets/[^"\s]+)', html)
|
|
assert assets, 'Dashboard entry must reference compiled assets'
|
|
for asset in assets:
|
|
assert wheel.read('treg/web/dashboard/' + asset)
|
|
assert not [name for name in wheel.namelist() if 'dashboard-legacy' in name]
|
|
arena = wheel.read('treg/web/enrich-arena.html').decode()
|
|
for asset in re.findall(r'src="(/vendor/vue-[^"\s]+)"', arena):
|
|
assert wheel.read('treg/web' + asset)
|
|
assert wheel.read('treg/web/vendor/LICENSE')
|
|
with open_tar(next(Path('dist').glob('*.tar.gz'))) as source:
|
|
assert not [member.name for member in source.getmembers()
|
|
if member.name.endswith('.mp4')]
|
|
PYCODE
|