Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
f057edb
feat(authoring): add draft domain model, validation findings and pers…
zjncs Sep 19, 2026
be5f72d
feat(authoring): run three-layer validation with isolated runtimes an…
zjncs Sep 19, 2026
4c71aa6
feat(authoring): expose draft authoring REST API
zjncs Sep 19, 2026
f4b243b
feat(web): add skill authoring workbench
zjncs Sep 19, 2026
8de9574
test(authoring): cover the authoring flow end to end
zjncs Sep 19, 2026
583258a
docs(authoring): document the platform, example skill and RISC-V64 ve…
zjncs Sep 19, 2026
065bd7f
feat(authoring): enforce endpoint, stdio and env security policies
zjncs Sep 21, 2026
1e1ec20
test(authoring): security regression coverage and smoke guards
zjncs Sep 21, 2026
3fe5116
docs(authoring): publish pipeline evidence and RISC-V re-verify script
zjncs Sep 21, 2026
efb78b6
fix(authoring): block the RFC 2544 range and make DNS tests hermetic
zjncs Oct 1, 2026
d02e02b
feat(riscv64): one-shot native verification setup
zjncs Oct 2, 2026
73a8484
ci(riscv64): run the authoring verification on real boards
zjncs Oct 2, 2026
c2b8ed8
docs(riscv64): record the real-board CI evidence
zjncs Oct 2, 2026
1bf473c
ci(riscv64): docker execution-mode job and full test-suite job on boards
zjncs Oct 6, 2026
310ac34
ci(riscv64): diagnose docker-mode failure and drop the unavailable board
zjncs Oct 8, 2026
93754cc
ci(riscv64): make docker-mode verification conditional on mount seman…
zjncs Oct 8, 2026
a1464ba
test(authoring): assume bind-mounts work in the docker adapter test
zjncs Oct 8, 2026
30599ac
ci(riscv64): free board disk after the test suite and record the gree…
zjncs Oct 8, 2026
fc3b6bf
docs(riscv64): record the fully green board run
zjncs Oct 8, 2026
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
227 changes: 227 additions & 0 deletions .github/workflows/riscv64-native.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
name: riscv64-native

# Real-riscv64 CI on the free Cloud-V / 10xEngineers board runners
# (https://10x-engineers.github.io/riscv-ci-partners/). Each job provisions a
# fresh container on a physical board. Guarded to the fork on purpose:
# iflytek/skillhub has no such runner, an unguarded job would queue forever.
#
# authoring-on-riscv64 — jar boot, scripts run natively (every push)
# authoring-docker-mode-on-riscv64 — jar boot with execution-mode=docker:
# validation scripts run inside the
# alpine:3.20 riscv64 container (every push)
# backend-tests-on-riscv64 — full ./mvnw test suite on a Banana Pi F3
# (manual dispatch only: multi-hour, and a
# different board type so it never blocks
# the quick verification jobs)
on:
workflow_dispatch:
push:
branches: [feat/skill-authoring-validation]

jobs:
authoring-on-riscv64:
if: github.repository == 'zjncs/skillhub'
runs-on: [self-hosted, visionfive2]
timeout-minutes: 180
steps:
- uses: actions/checkout@v4

- name: Record the board (native-proof evidence)
run: |
uname -a
head -5 /proc/cpuinfo
if grep -qi qemu /proc/cpuinfo; then
echo "WARNING: qemu markers in cpuinfo — this board looks emulated"
else
echo "OK: no qemu markers in cpuinfo"
fi

- name: Install JDK 21, PostgreSQL, Redis
run: |
sudo apt-get update
sudo apt-get install -y postgresql redis-server curl unzip
if ! sudo apt-get install -y openjdk-21-jdk-headless; then
echo "apt JDK 21 unavailable — falling back to the Temurin riscv64 tarball"
curl -fsSL "https://api.adoptium.net/v3/binary/latest/21/ga/linux/riscv64/jdk/hotspot/normal/eclipse" -o /tmp/jdk21.tgz
mkdir -p "$HOME/jdk21"
tar -xzf /tmp/jdk21.tgz -C "$HOME/jdk21" --strip-components=1
echo "$HOME/jdk21/bin" >> "$GITHUB_PATH"
"$HOME/jdk21/bin/java" -version
else
java -version
fi

- name: Start services and create the empty database
run: |
sudo service postgresql start
sudo service redis-server start
sudo -u postgres psql -v ON_ERROR_STOP=1 <<'SQL'
DO $$ BEGIN
IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'skillhub') THEN
CREATE ROLE skillhub LOGIN PASSWORD 'skillhub_dev';
END IF;
END $$;
SQL
sudo -u postgres psql -c "DROP DATABASE IF EXISTS skillhub_riscv_ci;"
sudo -u postgres psql -c "CREATE DATABASE skillhub_riscv_ci OWNER skillhub;"

- name: Native riscv64 build (Maven on the board)
run: |
cd server
./mvnw -B package -pl skillhub-app -am -DskipTests

- name: Boot the server and drive the authoring flow
run: |
SKILLHUB_RISCV_BOOT_MODE=jar SKILLHUB_RISCV_DB=skillhub_riscv_ci \
bash scripts/riscv64-verify.sh

authoring-docker-mode-on-riscv64:
if: github.repository == 'zjncs/skillhub'
runs-on: [self-hosted, visionfive2]
timeout-minutes: 180
steps:
- uses: actions/checkout@v4

- name: Record the board (native-proof evidence)
run: |
uname -a
if grep -qi qemu /proc/cpuinfo; then
echo "WARNING: qemu markers in cpuinfo — this board looks emulated"
else
echo "OK: no qemu markers in cpuinfo"
fi

- name: Install JDK 21, PostgreSQL, Redis
run: |
sudo apt-get update
sudo apt-get install -y postgresql redis-server curl unzip
if ! sudo apt-get install -y openjdk-21-jdk-headless; then
curl -fsSL "https://api.adoptium.net/v3/binary/latest/21/ga/linux/riscv64/jdk/hotspot/normal/eclipse" -o /tmp/jdk21.tgz
mkdir -p "$HOME/jdk21"
tar -xzf /tmp/jdk21.tgz -C "$HOME/jdk21" --strip-components=1
echo "$HOME/jdk21/bin" >> "$GITHUB_PATH"
fi
java -version

- name: Start services and create the empty database
run: |
sudo service postgresql start
sudo service redis-server start
sudo -u postgres psql -v ON_ERROR_STOP=1 <<'SQL'
DO $$ BEGIN
IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'skillhub') THEN
CREATE ROLE skillhub LOGIN PASSWORD 'skillhub_dev';
END IF;
END $$;
SQL
sudo -u postgres psql -c "DROP DATABASE IF EXISTS skillhub_riscv_ci_docker;"
sudo -u postgres psql -c "CREATE DATABASE skillhub_riscv_ci_docker OWNER skillhub;"

- name: Native riscv64 build (Maven on the board)
run: |
cd server
./mvnw -B package -pl skillhub-app -am -DskipTests

# Pre-flight: the docker execution-mode bind-mounts the workspace into
# the container. Runners whose job container shares the HOST docker
# daemon resolve -v sources against the host filesystem, where the job
# workspace does not exist — the mount comes up empty and the mode is
# unverifiable there (not a code defect; local Docker verifies fine).
# Probe the semantics, record them, and only run the timed verification
# when bind-mounts actually round-trip.
- name: Docker pre-flight (image pull + bind-mount round-trip)
id: docker_preflight
run: |
docker version --format 'client {{.Client.Version}} / server {{.Server.Version}}'
docker pull alpine:3.20
mkdir -p "$PWD/.docker-probe" && echo bind-mount-OK > "$PWD/.docker-probe/probe.txt"
if docker run --rm -v "$PWD/.docker-probe:/probe" alpine:3.20 cat /probe/probe.txt | grep -q bind-mount-OK; then
echo "bind-mount round-trip OK — docker execution-mode is verifiable on this runner"
echo "mount_ok=true" >> "$GITHUB_OUTPUT"
else
echo "bind-mount round-trip FAILED: this runner's docker daemon resolves -v sources"
echo "against the HOST filesystem, where the job workspace path does not exist."
echo "docker execution-mode (workspace bind-mount) cannot be verified on this runner"
echo "class — local-Docker evidence (backend=docker in the event stream) stands in"
echo "docs/26-skill-authoring-platform.md instead."
echo "mount_ok=false" >> "$GITHUB_OUTPUT"
fi
rm -rf "$PWD/.docker-probe"

- name: Verify with execution-mode=docker (scripts run in alpine:3.20 riscv64)
if: steps.docker_preflight.outputs.mount_ok == 'true'
env:
SKILLHUB_AUTHORING_LOCAL_SCRIPT_MODE: docker
run: |
SKILLHUB_RISCV_BOOT_MODE=jar SKILLHUB_RISCV_DB=skillhub_riscv_ci_docker \
bash scripts/riscv64-verify.sh
echo "NOTE: the TOOL_RESULT event in the log above must say \"backend\":\"docker\""

backend-tests-on-riscv64:
if: github.repository == 'zjncs/skillhub' && github.event_name == 'workflow_dispatch'
# bpi-f3 boards never provisioned a runner; the VisionFive 2 pool works.
# Jobs on the same board type run sequentially — fine for a manual run.
runs-on: [self-hosted, visionfive2]
timeout-minutes: 300
steps:
- uses: actions/checkout@v4

- name: Record the board (native-proof evidence)
run: |
uname -a
head -5 /proc/cpuinfo
if grep -qi qemu /proc/cpuinfo; then
echo "WARNING: qemu markers in cpuinfo — this board looks emulated"
else
echo "OK: no qemu markers in cpuinfo"
fi
docker version 2>/dev/null | head -4 || echo "docker not available in job"

- name: Install JDK 21
run: |
sudo apt-get update
sudo apt-get install -y curl unzip
if ! sudo apt-get install -y openjdk-21-jdk-headless; then
curl -fsSL "https://api.adoptium.net/v3/binary/latest/21/ga/linux/riscv64/jdk/hotspot/normal/eclipse" -o /tmp/jdk21.tgz
mkdir -p "$HOME/jdk21"
tar -xzf /tmp/jdk21.tgz -C "$HOME/jdk21" --strip-components=1
echo "$HOME/jdk21/bin" >> "$GITHUB_PATH"
fi
java -version

# Testcontainers pulls these during the suite; pulling first keeps the
# pull time out of the test timeouts (images stay cached on the board).
- name: Pre-pull Testcontainers images
run: |
docker pull postgres:16-alpine
docker pull alpine:3.20

# Best-effort probe: record whether a system browser exists on riscv64.
# Playwright ships no riscv64 driver, so full browser E2E stays on the
# dev machine; this step documents browser availability for the record.
- name: Probe system browser availability (best-effort)
continue-on-error: true
run: |
sudo apt-get install -y chromium-browser 2>/dev/null \
|| sudo apt-get install -y chromium 2>/dev/null \
|| sudo apt-get install -y firefox-esr 2>/dev/null \
|| true
(chromium --version || chromium-browser --version || firefox-esr --version) 2>/dev/null \
|| echo "no system browser package available on this riscv64 distro"

- name: Full backend test suite on riscv64
env:
TESTCONTAINERS_RYUK_DISABLED: "true"
run: |
cd server
./mvnw -B test

# The board's small disk fills up with the Maven repo, images and build
# artifacts; freeing space before the job ends keeps the runner's
# post-job cleanup (which failed once with disk pressure) healthy.
- name: Free disk space for the runner
if: always()
run: |
rm -rf server/*/target || true
docker image prune -f || true
df -h / | tail -1
12 changes: 7 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,18 @@ DEV_SERVER_PID := $(DEV_DIR)/server.pid
DEV_WEB_PID := $(DEV_DIR)/web.pid
DEV_SERVER_LOG := $(DEV_DIR)/server.log
DEV_WEB_LOG := $(DEV_DIR)/web.log
DEV_WEB_URL := http://localhost:3000
DEV_WEB_PORT ?= 3000
DEV_WEB_URL := http://localhost:$(DEV_WEB_PORT)
DEV_WEB_HOST ?= 127.0.0.1
DEV_API_URL := http://localhost:8080
DEV_API_PORT ?= 8080
DEV_API_URL := http://localhost:$(DEV_API_PORT)
DEV_SCANNER_URL := http://localhost:8000
STAGING_API_URL := http://localhost:8080
STAGING_WEB_URL := http://localhost
STAGING_SERVER_IMAGE := skillhub-server:staging
DEV_PROCESS := bash scripts/dev-process.sh
DEV_SERVER_PREPARE := true
DEV_SERVER_CMD := ./scripts/run-dev-app.sh
DEV_SERVER_CMD := ./scripts/run-dev-app.sh --server.port=$(DEV_API_PORT)
DEV_SERVER_SCANNER_ENV := SKILLHUB_SECURITY_SCANNER_ENABLED=true SKILLHUB_SECURITY_SCANNER_URL=$(DEV_SCANNER_URL) SKILLHUB_SECURITY_SCANNER_MODE=upload
BACKEND_TEST_JAVA_OPTIONS ?= -XX:+EnableDynamicAgentLoading
PARALLEL_BASE_REF ?= origin/main
Expand Down Expand Up @@ -49,7 +51,7 @@ dev-all: ## 一键启动本地开发环境(依赖 + scanner + 后端 + 前端
echo "Frontend already running with PID $$(cat $(DEV_WEB_PID))"; \
else \
echo "Starting frontend..."; \
$(DEV_PROCESS) start --pid-file $(DEV_WEB_PID) --log-file $(DEV_WEB_LOG) --cwd web -- pnpm exec vite --host $(DEV_WEB_HOST) --strictPort >/dev/null; \
$(DEV_PROCESS) start --pid-file $(DEV_WEB_PID) --log-file $(DEV_WEB_LOG) --cwd web -- env VITE_API_PROXY_TARGET=$(DEV_API_URL) pnpm exec vite --host $(DEV_WEB_HOST) --port $(DEV_WEB_PORT) --strictPort >/dev/null; \
fi
@echo "Waiting for backend on $(DEV_API_URL) ..."
@backend_ready=0; \
Expand Down Expand Up @@ -250,7 +252,7 @@ web-install-ci: ## 以 CI 方式安装前端依赖
cd web && CI=true pnpm install --frozen-lockfile

dev-web: ## 启动前端开发服务器
cd web && pnpm exec vite --host $(DEV_WEB_HOST)
cd web && VITE_API_PROXY_TARGET=$(DEV_API_URL) pnpm exec vite --host $(DEV_WEB_HOST) --port $(DEV_WEB_PORT)

build-frontend: web-deps ## 构建前端
cd web && pnpm run build
Expand Down
Loading