diff --git a/.dagger/ci/verify.dag b/.dagger/ci/verify.dag new file mode 100644 index 00000000..cb882b2c --- /dev/null +++ b/.dagger/ci/verify.dag @@ -0,0 +1,46 @@ +#!/usr/bin/env dagger + +# Portable Dev Plane CI contract. +# +# Woodpecker owns forge-triggered scheduling and exact checkout selection. +# Dagger owns the hermetic toolchain/execution environment. Repository-native +# Make/npm commands remain the behavioral source of truth, matching +# .github/workflows/ci.yml rather than creating a second test definition. +# +# Run locally with Dagger v0.20.3: +# dagger --progress=plain .dagger/ci/verify.dag +# +# Preserve .git so committed-range verification and revision diagnostics operate +# on the actual checkout. node_modules is intentionally excluded and installed +# cleanly. +src=$(host | directory . --exclude node_modules) + +container | + from node:24-bookworm | + with-directory /src $src | + with-workdir /src | + with-mounted-cache /root/go/pkg/mod dev-plane-go-mod | + with-mounted-cache /root/.cache/go-build dev-plane-go-build | + with-mounted-cache /root/.npm dev-plane-npm | + with-env-variable PATH /usr/local/go/bin:/root/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin | + with-env-variable DATABASE_URL "file:./data/dev.db?_journal_mode=WAL" | + with-env-variable NATS_URL nats://127.0.0.1:4222 | + with-env-variable JWT_SECRET ci-secret-change-me-min-32-chars-long | + with-env-variable SECRET_ENCRYPTION_KEYS "primary:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" | + with-env-variable NEXT_PUBLIC_API_URL http://localhost:8080 | + with-exec -- sh -lc 'apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends build-essential ca-certificates curl git make && rm -rf /var/lib/apt/lists/*' | + with-exec -- sh -lc 'curl -fsSLo /tmp/go.tgz https://go.dev/dl/go1.26.8.linux-amd64.tar.gz && echo "d0f743b33e8d8945e6b1f432edd15785c70507121d6e2a723b21285eddf8b57b /tmp/go.tgz" | sha256sum -c - && rm -rf /usr/local/go && tar -C /usr/local -xzf /tmp/go.tgz && rm /tmp/go.tgz' | + with-exec -- go version | + with-exec -- node --version | + with-exec -- npm --version | + with-exec -- go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.13.2 | + with-exec -- go install github.com/nats-io/nats-server/v2@v2.10.0 | + with-exec -- sh -lc 'cd apps/web && npm ci' | + with-exec -- make lint-go | + with-exec -- make lint-web | + with-exec -- sh -lc 'cd apps/web && npm run typecheck' | + with-exec -- make lint-sdk | + with-exec -- sh -lc 'set -eu; rm -rf /tmp/dev-plane-jetstream; nats-server --js --http_port 8222 --store_dir /tmp/dev-plane-jetstream >/tmp/nats.log 2>&1 & i=0; until curl -fsS http://127.0.0.1:8222/healthz >/dev/null; do i=$((i + 1)); if [ "$i" -ge 30 ]; then cat /tmp/nats.log; exit 1; fi; sleep 1; done; make test; cd apps/web && npm test' | + with-exec -- make build | + with-exec -- sh -lc 'set -eu; if git rev-parse --verify origin/main >/dev/null 2>&1; then if [ "$(git rev-parse HEAD)" = "$(git rev-parse origin/main)" ]; then git diff --check HEAD^..HEAD; else base=$(git merge-base HEAD origin/main); git diff --check "$base"..HEAD; fi; else git diff --check HEAD^..HEAD; fi' | + sync diff --git a/.woodpecker/README.md b/.woodpecker/README.md new file mode 100644 index 00000000..f045f4cf --- /dev/null +++ b/.woodpecker/README.md @@ -0,0 +1,56 @@ +# Woodpecker + Dagger CI + +Dev Plane uses Woodpecker as the forge-triggered scheduler and Dagger as the portable execution environment. + +## Authority + +The layers have deliberately narrow responsibilities: + +1. **Forge** selects the immutable revision and emits the webhook/event. +2. **Woodpecker** clones that revision, verifies `git rev-parse HEAD == CI_COMMIT_SHA`, and supplies owned compute. +3. **Dagger** provisions the pinned toolchain and executes the repository-native checks. +4. **Make/npm** remain the behavioral source of truth for lint, test, and build semantics. + +A successful run is evidence only for the exact `CI_COMMIT_SHA` that Woodpecker checked out. + +## Security boundary + +PR-controlled workflow code must never receive the Woodpecker host Docker socket. `.woodpecker/dagger.yml` therefore contains no host volume mounts and explicitly fails if `/var/run/docker.sock` is visible. + +The Dagger Engine must be operator-managed outside repository-controlled workflow configuration and exposed through `_EXPERIMENTAL_DAGGER_RUNNER_HOST`, injected by the Woodpecker control plane (for example through `WOODPECKER_ENVIRONMENT`). Keep the engine on disposable/dedicated CI infrastructure with no tenant secrets or production credentials. + +Dagger currently requires a privileged engine; the isolation boundary is therefore the dedicated CI execution host/VM, not the PR step container. Do not colocate this bootstrap engine with Nulang Cloud tenant or production Firecracker hosts. + +## Canonical lane + +`.woodpecker/dagger.yml` invokes `.dagger/ci/verify.dag` on the `bootstrap-ci` pool. + +The Dagger contract preserves the current repository CI requirements: + +- Node 24 +- Go 1.26.8 +- golangci-lint v2.13.2 +- NATS 2.10 with JetStream during tests +- `make lint-go` +- web lint and typecheck +- TypeScript SDK typecheck +- Go + SDK tests +- web tests +- `make build` +- `git diff --check` over the committed PR range (or the latest main commit) + +Dagger caches Go modules, the Go build cache, and npm downloads. The workflow pins Dagger v0.20.3, matching the already-operated Adacavo lane while keeping the execution engine outside PR-controlled Docker authority. + +## Activation + +The repository must be activated in the Woodpecker control plane so the forge webhook can create pipelines. Checking in this workflow cannot perform account/control-plane activation by itself. + +Nulang Cloud's `deploy/woodpecker/ensure-repository.sh` provides the operator-only activation/repair path. + +Keep GitHub Actions as a non-authoritative compatibility lane while the personal-account hosted-runner admission issue persists. Do not interpret `runner_id=0` / zero-step GitHub jobs as source verification failures. + +## Execution-plane migration + +Keep the required lane on `pool: bootstrap-ci` until the shared `k8s-ci`/Kueue execution plane has repeated scheduling, execution, terminal-status, and cleanup evidence. Changing the Woodpecker execution pool must not change `.dagger/ci/verify.dag` or the repository verification semantics. + +Nulang Cloud/Firecracker remains the future stronger verification runtime behind the Workspace contract; it is not required for this bootstrap CI lane. diff --git a/.woodpecker/dagger.yml b/.woodpecker/dagger.yml new file mode 100644 index 00000000..0f38df10 --- /dev/null +++ b/.woodpecker/dagger.yml @@ -0,0 +1,46 @@ +# Forge-triggered Dev Plane verification through the owned Woodpecker runner +# and the portable Dagger execution contract in .dagger/ci/verify.dag. +# +# SECURITY: PR-controlled workflow code must never receive the Woodpecker host +# Docker socket. The Dagger Engine is operator-managed outside this workflow and +# its endpoint is injected by the Woodpecker control plane via +# _EXPERIMENTAL_DAGGER_RUNNER_HOST. + +clone: + git: + image: docker.io/woodpeckerci/plugin-git + settings: + fetch-target-branch: true + partial: false + +labels: + platform: linux/amd64 + pool: bootstrap-ci + +when: + - event: pull_request + branch: main + - event: push + branch: main + - event: manual + +concurrency: + limit: 1 + group: dev-plane-dagger-ci + +steps: + - name: dagger-ci + image: debian:bookworm-slim + commands: + - set -eux + - test "$(git rev-parse HEAD)" = "$CI_COMMIT_SHA" + - echo "validating exact Woodpecker head $CI_COMMIT_SHA" + - test -n "$${_EXPERIMENTAL_DAGGER_RUNNER_HOST:-}" + - test ! -S /var/run/docker.sock + - case "$${_EXPERIMENTAL_DAGGER_RUNNER_HOST}" in unix:///var/run/docker.sock|unix:///run/docker.sock) echo 'refusing host Docker socket as Dagger runner' >&2; exit 1 ;; esac + - apt-get update -qq && apt-get install -y -qq curl ca-certificates + - curl -fsSL -o /tmp/dagger.tgz https://github.com/dagger/dagger/releases/download/v0.20.3/dagger_v0.20.3_linux_amd64.tar.gz + - echo '1a0a4779592c5136725c4839ae71dbfc3aaa59bb22cd4b01f2ca55a583f26b9d /tmp/dagger.tgz' | sha256sum -c - + - tar -xzf /tmp/dagger.tgz -C /usr/local/bin dagger + - dagger version + - dagger --progress=plain .dagger/ci/verify.dag