Problem
The only CI workflow today is docker-publish.yml, triggered on release to build and push the Docker image. There is no automated test execution on pull requests — tests are run manually before merging.
This means regressions can slip through, and contributors get no feedback until someone remembers to run pytest locally.
Goal
A tiered CI pipeline that runs the right level of testing at the right time:
| Tier |
Tests |
Trigger |
Runner |
~Duration |
| 1 |
Unit tests |
Every PR push |
ubuntu-latest |
~1 min |
| 2 |
Integration + Docker build |
Every PR push (vault/ changed) |
ubuntu-latest |
~3 min |
| 3 |
E2E (live container + gRPC) |
e2e label / push to main / manual |
Self-hosted vault-ci |
~5 min |
Phase 0 — Self-Hosted CI Runner (OCI)
Provision a dedicated CI runner on OCI, reusing existing deployment/oci/ Terraform patterns.
Infrastructure
Why self-hosted
- enVector API credentials stay within controlled infrastructure
- Consistent environment (Docker, grpcurl, Python pre-installed)
- No per-minute billing for e2e runs
Phase 1 — CI Workflows
.github/workflows/ci.yml — Tier 1 + Tier 2
Triggers on pull_request targeting main.
concurrency:
group: ci-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
Path filter: skip entirely if only docs/, *.md, deployment/, .github/ changed.
Job: unit-tests
- Runner:
ubuntu-latest
- Steps:
- Checkout
- Setup Python 3.12 + pip cache (
actions/cache keyed on tests/requirements.txt + vault/requirements.txt)
- Install deps:
pip install -r vault/requirements.txt -r tests/requirements.txt
pytest tests/unit/ -v --tb=short --cov=vault --cov-report=xml -x
- Upload
coverage.xml artifact
Job: integration-tests (needs: unit-tests)
- Runner:
ubuntu-latest
- No
ENVECTOR_ENDPOINT set → vault_core.ensure_vault() runs in offline mode (local FHE keys only)
- Steps:
- Checkout + setup Python + pip cache
pytest tests/integration/ -v --tb=short --cov=vault --cov-report=xml
- Upload coverage artifact
Job: build-image (needs: unit-tests)
- Runner:
ubuntu-latest
- Steps:
- Checkout
- Setup Docker Buildx
docker buildx build --load -t rune-vault:ci-${{ github.sha }} vault/ (amd64 only)
docker save rune-vault:ci-${{ github.sha }} | gzip > /tmp/rune-vault-ci.tar.gz
- Upload image artifact via
actions/upload-artifact
Job: coverage-report (needs: unit-tests, integration-tests)
- Merge coverage XMLs → upload to Codecov (or post PR comment)
.github/workflows/e2e.yml — Tier 3
Triggers:
pull_request with label e2e
push to main
workflow_dispatch (manual)
Job: e2e-tests
- Runner:
self-hosted + vault-ci
- Needs the Docker image — either build inline or download from
ci.yml artifact
- Steps:
- Checkout
- Build (or load) Docker image
- Start container:
docker run -d --name vault-ci \
-p 50051:50051 -p 9090:9090 \
-e VAULT_TLS_DISABLE=true \
-e VAULT_AUDIT_LOG=stdout \
-e ENVECTOR_ENDPOINT=... \
-e ENVECTOR_API_KEY=... \
-e VAULT_TEAM_SECRET=$(openssl rand -hex 32) \
rune-vault:ci-${{ github.sha }}
- Poll
http://localhost:9090/health until ready (timeout 120s)
- Issue test token via admin API
- Run
scripts/e2e-test.sh (see Phase 2)
- Tear down container
Phase 2 — E2E Test Script
Test cases
| # |
Test |
Method |
Pass criteria |
| 1 |
Health check |
curl localhost:9090/health |
{"status": "healthy"} |
| 2 |
Metrics endpoint |
curl localhost:9090/metrics |
Contains vault_requests_total |
| 3 |
gRPC reflection |
grpcurl -plaintext localhost:50051 list |
Lists rune.vault.v1.VaultService |
| 4 |
gRPC health |
grpcurl ... grpc.health.v1.Health/Check |
SERVING |
| 5 |
GetPublicKey (valid token) |
grpcurl ... VaultService/GetPublicKey |
Response contains key_bundle_json |
| 6 |
Auth rejection (bad token) |
grpcurl ... VaultService/GetPublicKey |
UNAUTHENTICATED error code |
| 7 |
Audit log emitted |
docker logs vault-ci | grep '"method"' |
JSON with required fields (timestamp, user_id, method, status, source_ip) |
| 8 |
Audit alert on auth failure |
Send 5 bad tokens |
AUDIT_ALERT.*auth_failure_threshold in logs |
Dependencies
Priority
Medium — Prevents regressions, enables confident merges. Phase 0 (runner) can be done independently of workflow authoring.
Problem
The only CI workflow today is
docker-publish.yml, triggered on release to build and push the Docker image. There is no automated test execution on pull requests — tests are run manually before merging.This means regressions can slip through, and contributors get no feedback until someone remembers to run
pytestlocally.Goal
A tiered CI pipeline that runs the right level of testing at the right time:
ubuntu-latestubuntu-lateste2elabel / push to main / manualvault-ciPhase 0 — Self-Hosted CI Runner (OCI)
Provision a dedicated CI runner on OCI, reusing existing
deployment/oci/Terraform patterns.Infrastructure
deployment/ci/oci/main.tf— OCI Compute instanceVM.Standard.E5.Flex(2 OCPU / 8 GB RAM)deployment/ci/oci/startup-script.sh— bootstrap scriptself-hosted,vault-ciWhy self-hosted
Phase 1 — CI Workflows
.github/workflows/ci.yml— Tier 1 + Tier 2Triggers on
pull_requesttargetingmain.Path filter: skip entirely if only
docs/,*.md,deployment/,.github/changed.Job:
unit-testsubuntu-latestactions/cachekeyed ontests/requirements.txt+vault/requirements.txt)pip install -r vault/requirements.txt -r tests/requirements.txtpytest tests/unit/ -v --tb=short --cov=vault --cov-report=xml -xcoverage.xmlartifactJob:
integration-tests(needs:unit-tests)ubuntu-latestENVECTOR_ENDPOINTset →vault_core.ensure_vault()runs in offline mode (local FHE keys only)pytest tests/integration/ -v --tb=short --cov=vault --cov-report=xmlJob:
build-image(needs:unit-tests)ubuntu-latestdocker buildx build --load -t rune-vault:ci-${{ github.sha }} vault/(amd64 only)docker save rune-vault:ci-${{ github.sha }} | gzip > /tmp/rune-vault-ci.tar.gzactions/upload-artifactJob:
coverage-report(needs:unit-tests,integration-tests).github/workflows/e2e.yml— Tier 3Triggers:
pull_requestwith labele2epushtomainworkflow_dispatch(manual)Job:
e2e-testsself-hosted+vault-cici.ymlartifacthttp://localhost:9090/healthuntil ready (timeout 120s)scripts/e2e-test.sh(see Phase 2)Phase 2 — E2E Test Script
scripts/e2e-test.sh— standalone, runnable locally or in CITest cases
curl localhost:9090/health{"status": "healthy"}curl localhost:9090/metricsvault_requests_totalgrpcurl -plaintext localhost:50051 listrune.vault.v1.VaultServicegrpcurl ... grpc.health.v1.Health/CheckSERVINGgrpcurl ... VaultService/GetPublicKeykey_bundle_jsongrpcurl ... VaultService/GetPublicKeyUNAUTHENTICATEDerror codedocker logs vault-ci | grep '"method"'AUDIT_ALERT.*auth_failure_thresholdin logsDependencies
Priority
Medium — Prevents regressions, enables confident merges. Phase 0 (runner) can be done independently of workflow authoring.