yao/.github/workflows/unit-test-v1.yml
Max b9cb36e52b chore(env): update sandbox environment and unit test workflow for improved configuration
- Added new environment variables for Docker and K8s API ports to avoid conflicts and enhance flexibility.
- Updated the unit test workflow to reference the new K3D API port variable, ensuring consistency across configurations.
- Adjusted the K8s API port in the workflow to align with the updated environment settings for better integration.

Made-with: Cursor
2026-03-12 21:34:18 +08:00

534 lines
22 KiB
YAML

name: Unit Test V1
on:
workflow_dispatch:
inputs:
tags:
description: "Version"
env:
CI_VERSION: "1.0.0"
REPO_KUN: ${{ github.repository_owner }}/kun
REPO_XUN: ${{ github.repository_owner }}/xun
REPO_GOU: ${{ github.repository_owner }}/gou
YAO_DEV: ${{ github.WORKSPACE }}
YAO_ENV: development
YAO_ROOT: ${{ github.WORKSPACE }}/../app
YAO_HOST: 0.0.0.0
YAO_PORT: 5099
YAO_SESSION: "memory"
YAO_LOG: "./logs/application.log"
YAO_LOG_MODE: "TEXT"
YAO_JWT_SECRET: "bLp@bi!oqo-2U+hoTRUG"
YAO_DB_AESKEY: "ZLX=T&f6refeCh-ro*r@"
YAO_EXTENSION_ROOT: ${{ github.WORKSPACE }}/../extension
YAO_TEST_APPLICATION: ${{ github.WORKSPACE }}/../app
YAO_RUNTIME_MIN: 3
YAO_RUNTIME_MAX: 6
YAO_RUNTIME_HEAP_LIMIT: 1500000000
YAO_RUNTIME_HEAP_RELEASE: 10000000
YAO_RUNTIME_HEAP_AVAILABLE: 550000000
YAO_RUNTIME_PRECOMPILE: true
MYSQL_TEST_HOST: "127.0.0.1"
MYSQL_TEST_PORT: "3308"
MYSQL_TEST_USER: "test"
MYSQL_TEST_PASS: "123456"
REDIS_TEST_HOST: "127.0.0.1"
REDIS_TEST_PORT: "6379"
REDIS_TEST_DB: "2"
MONGO_TEST_HOST: "127.0.0.1"
MONGO_TEST_PORT: "27017"
MONGO_TEST_USER: "root"
MONGO_TEST_PASS: "123456"
PG_TEST_HOST: "127.0.0.1"
PG_TEST_PORT: "5432"
PG_TEST_USER: "test"
PG_TEST_PASS: "123456"
jobs:
# =============================================================================
# Environment Setup & Verification
# Build Yao, start services, connect Tai via gRPC tunnel, verify everything.
# No tests are run — this job validates the CI environment is healthy.
# =============================================================================
setup-and-verify:
runs-on: ubuntu-latest
services:
mongodb:
image: mongo:6.0
ports:
- 27017:27017
env:
MONGO_INITDB_ROOT_USERNAME: root
MONGO_INITDB_ROOT_PASSWORD: 123456
MONGO_INITDB_DATABASE: test
postgres:
image: postgres:14
ports:
- 5432:5432
env:
POSTGRES_USER: test
POSTGRES_PASSWORD: 123456
POSTGRES_DB: test
options: >-
--health-cmd="pg_isready -U test"
--health-interval=10s
--health-timeout=5s
--health-retries=5
strategy:
matrix:
go: ["1.25"]
steps:
# ==== Phase 1: Checkout & Setup ====
- name: Checkout Yao
uses: actions/checkout@v4
- name: Setup Build Environment
uses: ./.github/actions/setup-yao
with:
repo-kun: ${{ env.REPO_KUN }}
repo-xun: ${{ env.REPO_XUN }}
repo-gou: ${{ env.REPO_GOU }}
checkout-init: "true"
apple-private-key: ${{ secrets.APPLE_PRIVATE_KEY_USER }}
- name: Load sandbox-v2 env
run: grep -vE '^\s*#|^\s*$' .github/env/sandbox-v2.env >> $GITHUB_ENV
- name: Setup SQLite
run: |
mkdir -p ${{ github.WORKSPACE }}/../app/db
echo "YAO_DB_PRIMARY=${{ github.WORKSPACE }}/../app/db/yao.db" >> $GITHUB_ENV
- name: Start MySQL 8.0
run: |
docker run -d --name mysql \
-e MYSQL_RANDOM_ROOT_PASSWORD=true \
-e MYSQL_USER=${MYSQL_TEST_USER} \
-e MYSQL_PASSWORD=${MYSQL_TEST_PASS} \
-e MYSQL_DATABASE=test \
-p ${MYSQL_TEST_PORT}:3306 \
mysql:8.0 --port=3306 --sql-mode='' \
--character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci
for i in $(seq 1 30); do
if docker exec mysql mysqladmin ping -h 127.0.0.1 -u ${MYSQL_TEST_USER} -p${MYSQL_TEST_PASS} > /dev/null 2>&1; then
echo "MySQL ready"
break
fi
echo "Waiting for MySQL... ($i/30)"
sleep 2
done
- name: Start Redis
run: docker run --name redis -d -p 6379:6379 redis:6
# ==== Phase 2: Code Quality & Build ====
- name: Code Quality Check
run: |
make vet
make fmt-check
- name: Build Yao
run: go build -v -o $RUNNER_TEMP/yao .
- name: Build ci-token
run: go build -tags ci -v -o $RUNNER_TEMP/ci-token ./cmd/ci-token
- name: Extract tai binary from image
run: |
CID=$(docker create yaoapp/tai:latest)
docker cp "$CID":/usr/local/bin/tai $RUNNER_TEMP/tai
docker rm "$CID"
chmod +x $RUNNER_TEMP/tai
$RUNNER_TEMP/tai version || echo "tai binary extracted"
# ==== Phase 3: Prepare & Start Yao ====
- name: Prepare test app directory
run: |
cp -r ${{ github.WORKSPACE }}/../yao-init $RUNNER_TEMP/yao-test-app
mkdir -p $RUNNER_TEMP/yao-test-app/db
- name: Start Yao server
run: |
cd $RUNNER_TEMP/yao-test-app
YAO_ROOT=$(pwd) \
YAO_HOST=0.0.0.0 \
YAO_PORT=5099 \
YAO_GRPC_HOST=0.0.0.0 \
YAO_GRPC_PORT=9099 \
YAO_DB_DRIVER=sqlite3 \
YAO_DB_PRIMARY=$(pwd)/db/yao.db \
YAO_SESSION=memory \
YAO_ENV=development \
YAO_JWT_SECRET="${{ env.YAO_JWT_SECRET }}" \
YAO_DB_AESKEY="${{ env.YAO_DB_AESKEY }}" \
$RUNNER_TEMP/yao start &
# Wait for Yao HTTP to be ready (up to 120s)
for i in $(seq 1 60); do
if curl -sf http://127.0.0.1:5099/.well-known/yao > /dev/null 2>&1; then
echo "Yao HTTP ready"
curl -s http://127.0.0.1:5099/.well-known/yao | jq .
break
fi
echo "Waiting for Yao... ($i/60)"
sleep 2
done
curl -sf http://127.0.0.1:5099/.well-known/yao > /dev/null 2>&1 || {
echo "::error::Yao HTTP failed to start"
exit 1
}
# ==== Phase 4: Generate Tai credentials ====
- name: Generate Tai credentials
run: |
gen_cred() {
local CID=$1 TID=$2 OUT=$3
local TOKEN
TOKEN=$($RUNNER_TEMP/ci-token \
--app $RUNNER_TEMP/yao-test-app \
--client-id "$CID" \
--subject "${YAO_CI_OAUTH_SUBJECT:-ci-tai}" \
--user-id "${YAO_CI_OAUTH_USER_ID}" \
--team-id "${YAO_CI_OAUTH_TEAM_ID}" \
--scope "${YAO_CI_OAUTH_SCOPE:-tai:tunnel}" \
--ttl "${YAO_CI_OAUTH_TTL:-24h}" 2>/dev/null | tail -1 | tr -d '[:space:]')
local JSON="{\"client_id\":\"$CID\",\"tai_id\":\"$TID\",\"machine_id\":\"ci-runner\",\"server\":\"http://127.0.0.1:${YAO_CI_HTTP_PORT}\",\"yao_grpc_addr\":\"127.0.0.1:${YAO_CI_GRPC_PORT}\",\"access_token\":\"$TOKEN\",\"scope\":\"${YAO_CI_OAUTH_SCOPE}\",\"expires_at\":\"2099-01-01T00:00:00Z\",\"registered\":true}"
echo -n "$JSON" | base64 -w0 > "$OUT"
echo ""
echo "Credentials JSON (debug): $JSON" | head -c 200
echo "..."
echo "Generated credentials for $CID → $OUT"
}
gen_cred tai-ci-local tai-local-001 $RUNNER_TEMP/tai-local-credentials
gen_cred tai-ci-docker tai-docker-001 $RUNNER_TEMP/tai-docker-credentials
gen_cred tai-ci-k8s tai-k8s-001 $RUNNER_TEMP/tai-k8s-credentials
gen_cred tai-ci-hostexec tai-hostexec-001 $RUNNER_TEMP/tai-hostexec-credentials
# ==== Phase 5: Pull images & Setup K8s ====
- name: Pull test images
run: |
docker pull yaoapp/tai-sandbox-test:latest || true
docker pull yaoapp/tai:latest
docker pull alpine:latest
- name: Install k3d & create cluster
run: |
curl -s https://raw.githubusercontent.com/k3d-io/k3d/main/install.sh | bash
k3d cluster create tai-test --no-lb --wait --api-port ${YAO_CI_K3D_API_PORT}
kubectl wait --for=condition=Ready node --all --timeout=60s
k3d image import alpine:latest -c tai-test
- name: Generate kubeconfig
run: |
K3D_IP=$(docker inspect k3d-tai-test-server-0 | jq -r '.[0].NetworkSettings.Networks["k3d-tai-test"].IPAddress')
echo "k3d server IP: ${K3D_IP}"
k3d kubeconfig get tai-test > /tmp/kubeconfig-k3d.yml
# For tai-k8s container (uses k3d internal IP)
sed "s|server: .*|server: https://${K3D_IP}:6443|" /tmp/kubeconfig-k3d.yml \
> /tmp/kubeconfig-tai-k8s.yml
echo "Container kubeconfig server:"
grep server: /tmp/kubeconfig-tai-k8s.yml
# For test runner (uses localhost via k3d port-mapped API)
sed "s|server: .*|server: https://127.0.0.1:${YAO_CI_K3D_API_PORT}|" /tmp/kubeconfig-k3d.yml \
> $RUNNER_TEMP/kubeconfig-tai.yml
echo "Test runner kubeconfig server:"
grep server: $RUNNER_TEMP/kubeconfig-tai.yml
# Export for later steps
echo "TAI_TEST_KUBECONFIG=$RUNNER_TEMP/kubeconfig-tai.yml" >> $GITHUB_ENV
echo "YAO_CI_TAI_KUBECONFIG=$RUNNER_TEMP/kubeconfig-tai.yml" >> $GITHUB_ENV
# ==== Phase 6: Start Tai instances (host processes) ====
- name: Start tai-local (DIRECT mode, auto-detect Docker)
run: |
mkdir -p $RUNNER_TEMP/tai-local-data
TAI_CREDENTIALS=$RUNNER_TEMP/tai-local-credentials \
TAI_YAO_SERVER=http://127.0.0.1:${YAO_CI_HTTP_PORT} \
TAI_DATA_DIR=$RUNNER_TEMP/tai-local-data \
$RUNNER_TEMP/tai server \
--grpc 127.0.0.1:${YAO_CI_TAI_LOCAL_GRPC_PORT} \
--http 127.0.0.1:${YAO_CI_TAI_LOCAL_HTTP_PORT} \
--vnc 127.0.0.1:${YAO_CI_TAI_LOCAL_VNC_PORT} \
--docker 127.0.0.1:${YAO_CI_TAI_LOCAL_DOCKER_PORT} \
--direct \
--host-exec --host-exec-full-access \
--log-level debug &
echo $! > $RUNNER_TEMP/tai-local.pid
echo "tai-local PID: $(cat $RUNNER_TEMP/tai-local.pid)"
for i in $(seq 1 30); do
if curl -sf http://127.0.0.1:${YAO_CI_TAI_LOCAL_HTTP_PORT}/healthz > /dev/null 2>&1; then
echo "tai-local HTTP ready"
break
fi
echo "Waiting for tai-local HTTP... ($i/30)"
sleep 1
done
- name: Start tai-docker (TUNNEL mode, Docker API proxy)
run: |
mkdir -p $RUNNER_TEMP/tai-docker-data
TAI_CREDENTIALS=$RUNNER_TEMP/tai-docker-credentials \
TAI_YAO_SERVER=http://127.0.0.1:${YAO_CI_HTTP_PORT} \
TAI_DATA_DIR=$RUNNER_TEMP/tai-docker-data \
$RUNNER_TEMP/tai server \
--grpc 127.0.0.1:${YAO_CI_TAI_DOCKER_GRPC_PORT} \
--http 127.0.0.1:${YAO_CI_TAI_DOCKER_HTTP_PORT} \
--vnc 127.0.0.1:${YAO_CI_TAI_DOCKER_VNC_PORT} \
--docker 127.0.0.1:${YAO_CI_TAI_DOCKER_API_PORT} \
--host-exec --host-exec-full-access \
--log-level debug &
echo $! > $RUNNER_TEMP/tai-docker.pid
echo "tai-docker PID: $(cat $RUNNER_TEMP/tai-docker.pid)"
for i in $(seq 1 30); do
if curl -sf http://127.0.0.1:${YAO_CI_TAI_DOCKER_HTTP_PORT}/healthz > /dev/null 2>&1; then
echo "tai-docker HTTP ready"
break
fi
echo "Waiting for tai-docker HTTP... ($i/30)"
sleep 1
done
- name: Start tai-k8s (TUNNEL mode, K8s API proxy)
run: |
mkdir -p $RUNNER_TEMP/tai-k8s-data
TAI_CREDENTIALS=$RUNNER_TEMP/tai-k8s-credentials \
TAI_YAO_SERVER=http://127.0.0.1:${YAO_CI_HTTP_PORT} \
TAI_DATA_DIR=$RUNNER_TEMP/tai-k8s-data \
TAI_K8S_UPSTREAM="tcp://127.0.0.1:${YAO_CI_K3D_API_PORT}" \
TAI_KUBECONFIG=$RUNNER_TEMP/kubeconfig-tai.yml \
$RUNNER_TEMP/tai server \
--grpc 127.0.0.1:${YAO_CI_TAI_K8S_GRPC_PORT} \
--http 127.0.0.1:${YAO_CI_TAI_K8S_HTTP_PORT} \
--vnc 127.0.0.1:${YAO_CI_TAI_K8S_VNC_PORT} \
--k8s 127.0.0.1:${YAO_CI_TAI_K8S_API_PORT} \
--docker="" \
--host-exec --host-exec-full-access \
--log-level debug &
echo $! > $RUNNER_TEMP/tai-k8s.pid
echo "tai-k8s PID: $(cat $RUNNER_TEMP/tai-k8s.pid)"
for i in $(seq 1 30); do
if curl -sf http://127.0.0.1:${YAO_CI_TAI_K8S_HTTP_PORT}/healthz > /dev/null 2>&1; then
echo "tai-k8s HTTP ready"
break
fi
echo "Waiting for tai-k8s HTTP... ($i/30)"
sleep 1
done
- name: Start tai-hostexec (TUNNEL mode, HostExec only, no runtime)
run: |
mkdir -p $RUNNER_TEMP/tai-hostexec-data
TAI_CREDENTIALS=$RUNNER_TEMP/tai-hostexec-credentials \
TAI_YAO_SERVER=http://127.0.0.1:${YAO_CI_HTTP_PORT} \
TAI_DATA_DIR=$RUNNER_TEMP/tai-hostexec-data \
$RUNNER_TEMP/tai server \
--grpc 127.0.0.1:${YAO_CI_TAI_HOSTEXEC_GRPC_PORT} \
--http 127.0.0.1:${YAO_CI_TAI_HOSTEXEC_HTTP_PORT} \
--vnc 127.0.0.1:${YAO_CI_TAI_HOSTEXEC_VNC_PORT} \
--docker="" \
--host-exec --host-exec-full-access \
--log-level debug &
echo $! > $RUNNER_TEMP/tai-hostexec.pid
echo "tai-hostexec PID: $(cat $RUNNER_TEMP/tai-hostexec.pid)"
for i in $(seq 1 30); do
if curl -sf http://127.0.0.1:${YAO_CI_TAI_HOSTEXEC_HTTP_PORT}/healthz > /dev/null 2>&1; then
echo "tai-hostexec HTTP ready"
break
fi
echo "Waiting for tai-hostexec HTTP... ($i/30)"
sleep 1
done
# ==== Phase 7: Environment Verification (fail fast) ====
- name: Verify Environment
run: |
echo "CI Environment v${CI_VERSION}"
echo ""
FAILED=0
check() {
local name=$1; shift
if "$@" > /dev/null 2>&1; then
echo " [PASS] $name"
else
echo " [FAIL] $name"
FAILED=$((FAILED + 1))
fi
}
echo "=== Environment Verification ==="
# ── 1. Service Health ──
echo ""
echo "--- 1. Service Health ---"
echo "[Yao]"
check "Yao HTTP (/.well-known/yao)" curl -sf http://127.0.0.1:5099/.well-known/yao
check "Yao gRPC port" nc -z 127.0.0.1 9099
echo "[tai-local (DIRECT)]"
check "tai-local process alive" kill -0 $(cat $RUNNER_TEMP/tai-local.pid 2>/dev/null || echo 0)
check "tai-local HTTP (/healthz)" curl -sf http://127.0.0.1:${YAO_CI_TAI_LOCAL_HTTP_PORT}/healthz
check "tai-local gRPC reachable (direct)" nc -z 127.0.0.1 ${YAO_CI_TAI_LOCAL_GRPC_PORT}
echo "[tai-docker (TUNNEL)]"
check "tai-docker process alive" kill -0 $(cat $RUNNER_TEMP/tai-docker.pid 2>/dev/null || echo 0)
check "tai-docker HTTP (/healthz)" curl -sf http://127.0.0.1:${YAO_CI_TAI_DOCKER_HTTP_PORT}/healthz
check "tai-docker gRPC listener" nc -z 127.0.0.1 ${YAO_CI_TAI_DOCKER_GRPC_PORT}
echo "[tai-k8s (TUNNEL)]"
check "tai-k8s process alive" kill -0 $(cat $RUNNER_TEMP/tai-k8s.pid 2>/dev/null || echo 0)
check "tai-k8s HTTP (/healthz)" curl -sf http://127.0.0.1:${YAO_CI_TAI_K8S_HTTP_PORT}/healthz
check "tai-k8s gRPC listener" nc -z 127.0.0.1 ${YAO_CI_TAI_K8S_GRPC_PORT}
echo "[tai-hostexec (TUNNEL)]"
check "tai-hostexec process alive" kill -0 $(cat $RUNNER_TEMP/tai-hostexec.pid 2>/dev/null || echo 0)
check "tai-hostexec HTTP (/healthz)" curl -sf http://127.0.0.1:${YAO_CI_TAI_HOSTEXEC_HTTP_PORT}/healthz
check "tai-hostexec gRPC listener" nc -z 127.0.0.1 ${YAO_CI_TAI_HOSTEXEC_GRPC_PORT}
echo "[K8s (k3d via direct API)]"
check "kubectl get nodes (k3d direct)" kubectl --kubeconfig=$RUNNER_TEMP/kubeconfig-tai.yml get nodes
echo "[Data Stores]"
MONGO_CID=$(docker ps -qf "ancestor=mongo:6.0" | head -1)
check "MongoDB ping" docker exec "$MONGO_CID" mongosh --quiet \
-u ${MONGO_TEST_USER} -p ${MONGO_TEST_PASS} --authenticationDatabase admin \
--eval "db.runCommand({ping:1})"
check "MySQL ping" docker exec mysql mysqladmin ping -h 127.0.0.1 \
-u ${MYSQL_TEST_USER} -p${MYSQL_TEST_PASS}
PG_CID=$(docker ps -qf "ancestor=postgres:14" | head -1)
check "PostgreSQL ping" docker exec "$PG_CID" pg_isready -U ${PG_TEST_USER}
check "Redis ping" docker exec redis redis-cli ping
# ── 2. Network Topology ──
echo ""
echo "--- 2. Network Topology ---"
BRIDGE_IP=${YAO_CI_BRIDGE_IP}
echo "[Host network basics]"
check "docker0 bridge exists" ip addr show docker0
check "Bridge IP reachable (${BRIDGE_IP})" ping -c1 -W2 ${BRIDGE_IP}
check "Docker socket accessible" test -S /var/run/docker.sock
check "tai binary on runner" test -x $RUNNER_TEMP/tai
echo "[Yao endpoints (all Tai instances need these)]"
check "Yao HTTP :${YAO_CI_HTTP_PORT}" curl -sf http://127.0.0.1:${YAO_CI_HTTP_PORT}/.well-known/yao
check "Yao gRPC :${YAO_CI_GRPC_PORT}" nc -z 127.0.0.1 ${YAO_CI_GRPC_PORT}
echo "[DIRECT path: Yao → tai-local]"
check "Yao→tai-local gRPC :${YAO_CI_TAI_LOCAL_GRPC_PORT}" nc -z 127.0.0.1 ${YAO_CI_TAI_LOCAL_GRPC_PORT}
check "Yao→tai-local HTTP :${YAO_CI_TAI_LOCAL_HTTP_PORT}" curl -sf http://127.0.0.1:${YAO_CI_TAI_LOCAL_HTTP_PORT}/healthz
check "tai-local Docker API proxy :${YAO_CI_TAI_LOCAL_DOCKER_PORT}" nc -z 127.0.0.1 ${YAO_CI_TAI_LOCAL_DOCKER_PORT}
echo "[TUNNEL path: tai-docker → Yao gRPC (reverse tunnel)]"
check "tai-docker→Yao gRPC :${YAO_CI_GRPC_PORT}" nc -z 127.0.0.1 ${YAO_CI_GRPC_PORT}
check "tai-docker Docker API proxy :${YAO_CI_TAI_DOCKER_API_PORT}" nc -z 127.0.0.1 ${YAO_CI_TAI_DOCKER_API_PORT}
check "Docker API via proxy" bash -c "curl -sf http://127.0.0.1:${YAO_CI_TAI_DOCKER_API_PORT}/version | jq -r .ApiVersion"
echo "[TUNNEL path: tai-k8s → Yao gRPC (reverse tunnel)]"
check "tai-k8s→Yao gRPC :${YAO_CI_GRPC_PORT}" nc -z 127.0.0.1 ${YAO_CI_GRPC_PORT}
check "tai-k8s K8s API proxy :${YAO_CI_TAI_K8S_API_PORT}" nc -z 127.0.0.1 ${YAO_CI_TAI_K8S_API_PORT}
sed "s|server: .*|server: https://127.0.0.1:${YAO_CI_TAI_K8S_API_PORT}|" $RUNNER_TEMP/kubeconfig-tai.yml \
> $RUNNER_TEMP/kubeconfig-tai-proxy.yml
check "K8s API via tai-k8s proxy (kubectl)" kubectl --kubeconfig=$RUNNER_TEMP/kubeconfig-tai-proxy.yml --insecure-skip-tls-verify get nodes
echo "[TUNNEL path: tai-hostexec → Yao gRPC (reverse tunnel)]"
check "tai-hostexec→Yao gRPC :${YAO_CI_GRPC_PORT}" nc -z 127.0.0.1 ${YAO_CI_GRPC_PORT}
echo "[Data Store connectivity from runner]"
check "MongoDB :27017" nc -z 127.0.0.1 27017
check "Redis :6379" nc -z 127.0.0.1 6379
check "MySQL :${MYSQL_TEST_PORT}" nc -z 127.0.0.1 ${MYSQL_TEST_PORT}
check "PostgreSQL :${PG_TEST_PORT}" nc -z 127.0.0.1 ${PG_TEST_PORT}
# ── 3. Connection Mode Verification ──
echo ""
echo "--- 3. Connection Modes ---"
sleep 5
echo "[Credentials (4 tokens)]"
check "tai-local credentials exist" test -f $RUNNER_TEMP/tai-local-credentials
check "tai-docker credentials exist" test -f $RUNNER_TEMP/tai-docker-credentials
check "tai-k8s credentials exist" test -f $RUNNER_TEMP/tai-k8s-credentials
check "tai-hostexec credentials exist" test -f $RUNNER_TEMP/tai-hostexec-credentials
echo "[DIRECT: tai-local → Yao HTTP register → Yao dials tai-local gRPC]"
echo " tai-local registers via POST /tai-nodes/register"
echo " Yao dials back tai-local gRPC at 127.0.0.1:${YAO_CI_TAI_LOCAL_GRPC_PORT}"
echo "[TUNNEL: tai-docker → Yao gRPC :${YAO_CI_GRPC_PORT} (Register + Forward)]"
echo " Sandbox connects Yao gRPC → Forward stream → tai-docker :${YAO_CI_TAI_DOCKER_GRPC_PORT}"
echo "[TUNNEL: tai-k8s → Yao gRPC :${YAO_CI_GRPC_PORT} (Register + Forward)]"
echo " Sandbox connects Yao gRPC → Forward stream → tai-k8s :${YAO_CI_TAI_K8S_GRPC_PORT}"
echo "[TUNNEL: tai-hostexec → Yao gRPC :${YAO_CI_GRPC_PORT} (Register + Forward)]"
echo " HostExec only, no container runtime"
WELL_KNOWN=$(curl -sf http://127.0.0.1:5099/.well-known/yao 2>/dev/null || echo "{}")
echo ""
echo " Yao .well-known/yao:"
echo "$WELL_KNOWN" | jq . 2>/dev/null || echo " $WELL_KNOWN"
# ── 4. HostExec Readiness ──
echo ""
echo "--- 4. HostExec ---"
echo "[HostExec gRPC ports (all 4 instances)]"
check "HostExec tai-local (direct, auto-Docker)" nc -z 127.0.0.1 ${YAO_CI_TAI_LOCAL_GRPC_PORT}
check "HostExec tai-docker (tunnel, Docker proxy)" nc -z 127.0.0.1 ${YAO_CI_TAI_DOCKER_GRPC_PORT}
check "HostExec tai-k8s (tunnel, K8s proxy)" nc -z 127.0.0.1 ${YAO_CI_TAI_K8S_GRPC_PORT}
check "HostExec tai-hostexec (tunnel, no runtime)" nc -z 127.0.0.1 ${YAO_CI_TAI_HOSTEXEC_GRPC_PORT}
echo ""
echo "=========================================="
if [ $FAILED -gt 0 ]; then
echo "::error::$FAILED verification check(s) FAILED"
echo ""
echo "=== Diagnostic Info ==="
echo "--- Docker containers ---"
docker ps -a
echo ""
echo "--- Processes (yao + tai) ---"
ps aux | grep -E "yao|tai" | grep -v grep || true
echo ""
echo "--- Listening ports ---"
ss -tlnp | grep -E "5099|9099|19100|19101|19102|19103|8099|8100|8101|8102|12375|12376|16443|16444" || true
exit 1
else
echo "All verification checks PASSED"
fi