Skip to content
46 changes: 46 additions & 0 deletions .dagger/ci/verify.dag
Original file line number Diff line number Diff line change
@@ -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
56 changes: 56 additions & 0 deletions .woodpecker/README.md
Original file line number Diff line number Diff line change
@@ -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.
46 changes: 46 additions & 0 deletions .woodpecker/dagger.yml
Original file line number Diff line number Diff line change
@@ -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
Loading