27 KiB
Local CI/CD Pipeline — Design
Overview
This design replaces the GitHub-dependent CI/CD pipeline (ARC + GHCR) with a fully local pipeline using Gitea as the Git forge, Woodpecker CI for pipeline execution, and the existing local Docker registry at registry.celestium.life for image storage. The existing ArgoCD and Kargo infrastructure is retained for GitOps deployment and staged promotion, with configuration updates to point at local sources instead of GitHub/GHCR.
The migration touches five areas:
- Gitea configuration — Complete initial setup (admin user, OAuth2 app), create the
stonks-oraclerepository, and configure webhooks for Woodpecker CI. Gitea is already deployed in thegit-servernamespace but unconfigured. - Woodpecker CI deployment — Deploy server and agent via the
woodpecker/woodpeckerHelm chart in thewoodpeckernamespace. The server authenticates with Gitea via OAuth2. The agent uses the Kubernetes backend, executing each pipeline step as a standalone Pod. - Pipeline file — Create
.woodpecker.ymltranslating the existing GitHub Actions workflow into Woodpecker's native format, targeting the local registry and adding a GitHub mirror step. - ArgoCD/Kargo updates — Update ArgoCD repo secret to point at Gitea, update ArgoCD Applications to source from Gitea, update Kargo Warehouse to watch the local registry.
- ARC teardown — Remove ARC controller, runner scale set, RBAC, PV, and
arc-systemnamespace.
Key Design Decisions
-
Woodpecker with Kubernetes backend (not Docker-in-Docker agent) — The Woodpecker agent uses
WOODPECKER_BACKEND: kubernetes, executing each pipeline step as a standalone Pod in thewoodpeckernamespace. A temporary PVC is created per pipeline run to transfer files between steps. This avoids DinD complexity for most steps. Image builds use thewoodpeckerci/plugin-docker-buildxplugin with privileged mode for the build step only. -
Gitea API for initial setup — Gitea's initial setup (admin user creation, OAuth2 app registration, repo creation) is automated via Gitea's REST API in
runmefirst.sh. This avoids manual web UI interaction and makes the setup reproducible. -
Single Helm chart for Woodpecker — The
woodpecker/woodpeckerchart contains both server and agent subcharts. Onehelm installdeploys both components. The agent connects to the server via the in-cluster servicewoodpecker-server:9000. -
NFS PV for Woodpecker — Woodpecker server data (SQLite database, build logs) persists on an NFS volume at
nfs://192.168.42.8:/volume1/Kubernetes/pipelines/woodpecker, surviving cluster rebuilds. The ARC PV is removed since ARC is being torn down. -
GitHub as read-only mirror — After all CI steps pass, a final pipeline step pushes to GitHub via SSH key stored as a Woodpecker secret. GitHub mirror failure does not block image promotion or deployment.
-
ArgoCD sources from Gitea — ArgoCD's repo secret is updated to point at the Gitea repository URL. All three Applications (beta, paper, live) source Helm charts from Gitea instead of GitHub.
-
Helm chart image registry update — The base
values.yamlchangesimage.registryfromghcr.io/celesrenata/stonks-oracletoregistry.celestium.life/stonks-oracle. TheghcrAuthsection andghcr-credentialsimagePullSecret are removed since the local registry requires no authentication.
Architecture
┌─────────────────────────────────────────────────────────────────────────────┐
│ Gremlin Cluster (4x NixOS) │
│ │
│ ┌─────────────────┐ ┌──────────────────┐ ┌───────────────────────────┐ │
│ │ git-server ns │ │ woodpecker ns │ │ argocd ns │ │
│ │ (pre-existing) │ │ (NEW) │ │ (existing, updated) │ │
│ │ │ │ │ │ │ │
│ │ Gitea │ │ WP Server │ │ ArgoCD Server │ │
│ │ 10.1.1.x:30300 │ │ (StatefulSet) │ │ (stonks-argocd. │ │
│ │ :30022 (SSH) │ │ stonks-ci. │ │ celestium.life) │ │
│ │ │ │ celestium.life │ │ │ │
│ │ Local Registry │ │ │ │ Repo: Gitea (updated) │ │
│ │ registry. │ │ WP Agent │ │ │ │
│ │ celestium.life │ │ (Deployment) │ │ │ │
│ │ :30500 │ │ K8s backend │ │ │ │
│ └─────────────────┘ └──────────────────┘ └───────────────────────────┘ │
│ │
│ ┌─────────────────┐ ┌──────────────────┐ ┌───────────────────────────┐ │
│ │ kargo ns │ │ stonks-beta ns │ │ stonks-oracle ns │ │
│ │ (existing, │ │ │ │ (live/production) │ │
│ │ updated) │ │ ArgoCD App: │ │ │ │
│ │ │ │ stonks-beta │ │ ArgoCD App: stonks-live │ │
│ │ Warehouse: │ │ images from │ │ images from │ │
│ │ local registry │ │ local registry │ │ local registry │ │
│ │ (updated) │ │ │ │ │ │
│ └─────────────────┘ └──────────────────┘ └───────────────────────────┘ │
│ │
│ NFS: nfs://192.168.42.8:/volume1/Kubernetes/pipelines/{argocd,kargo,woodpecker}│
└─────────────────────────────────────────────────────────────────────────────┘
Pipeline Flow
graph LR
A[Git Push to Gitea] --> B[Webhook → Woodpecker CI]
B --> C[Lint + Test<br/>Python ruff + pytest<br/>Frontend vitest]
C --> D[Build + Push<br/>all images to<br/>local registry]
D --> E[Integration Tests<br/>run_pipeline.sh]
E -->|pass| F[GitHub Mirror<br/>git push]
E -->|fail| X[❌ Pipeline Failed]
F --> G[Kargo Warehouse<br/>detects new tag<br/>in local registry]
G --> H[Beta Stage<br/>auto-promote]
H --> I{Market Hours?}
I -->|outside| J[Paper Stage]
I -->|during| K[🚫 Blocked]
K -->|break-glass| J
J --> L{Market Hours?}
L -->|outside| M[Live Stage<br/>manual approval]
L -->|during| N[🚫 Blocked]
N -->|break-glass| M
Components and Interfaces
1. Gitea Configuration (pipelines/gitea/)
Gitea is already deployed in the git-server namespace but needs initial setup. The configuration is automated via shell scripts that call Gitea's REST API.
Setup Steps (in runmefirst.sh):
- Complete initial setup — POST to
http://<gitea-svc>:3000/with admin credentials to complete the install wizard, or use the Gitea API to create the admin user if the instance is already initialized. - Create OAuth2 application — POST to
/api/v1/user/applications/oauth2to register Woodpecker CI with callback URLhttps://stonks-ci.celestium.life/authorize. Store the returnedclient_idandclient_secretfor Woodpecker's Helm values. - Create repository — POST to
/api/v1/user/reposto createstonks-oraclerepository. - Add Gitea remote to local repo — Configure the local Git clone on gremlin-1 with the Gitea remote and push the existing codebase.
Gitea Service Access:
- Web UI:
http://gitea-http.git-server.svc.cluster.local:3000(cluster-internal) /10.1.1.x:30300(NodePort) - SSH:
:30022(NodePort) - API:
http://gitea-http.git-server.svc.cluster.local:3000/api/v1/
Webhook Configuration: Woodpecker CI automatically registers webhooks when a repository is activated through the Woodpecker dashboard or API. The webhook URL points to the Woodpecker server's internal service endpoint.
2. Woodpecker CI Server and Agent (pipelines/woodpecker/)
Namespace: woodpecker
Helm Chart: woodpecker/woodpecker from the woodpecker-ci/helm repository. Contains two subcharts: server and agent.
Server Configuration:
- StatefulSet with 1 replica
- Persistent volume for SQLite database and build data at
/var/lib/woodpecker - NFS-backed PV at
nfs://192.168.42.8:/volume1/Kubernetes/pipelines/woodpecker - Traefik ingress at
stonks-ci.celestium.lifewith TLS viaca-issuer - Gitea OAuth2 authentication via
WOODPECKER_GITEA=true,WOODPECKER_GITEA_URL,WOODPECKER_GITEA_CLIENT,WOODPECKER_GITEA_SECRET WOODPECKER_HOST=https://stonks-ci.celestium.lifeWOODPECKER_ADMIN=admin(matches Gitea admin username)
Agent Configuration:
- Deployment with 2 replicas
- Kubernetes backend (
WOODPECKER_BACKEND: kubernetes) - Pipeline steps execute as standalone Pods in the
woodpeckernamespace - Temporary PVC created per pipeline run for file transfer between steps
WOODPECKER_BACKEND_K8S_STORAGE_CLASS: ""(use default)WOODPECKER_BACKEND_K8S_VOLUME_SIZE: 10G- ServiceAccount with RBAC for creating Pods, Services, PVCs in the
woodpeckernamespace - Additional ClusterRoleBinding for integration test steps that need to create ephemeral namespaces
Helm Values Structure (pipelines/woodpecker/values.yaml):
server:
enabled: true
env:
WOODPECKER_HOST: "https://stonks-ci.celestium.life"
WOODPECKER_GITEA: "true"
WOODPECKER_GITEA_URL: "http://gitea-http.git-server.svc.cluster.local:3000"
WOODPECKER_GITEA_CLIENT: "<from-oauth2-setup>"
WOODPECKER_GITEA_SECRET: "<from-oauth2-setup>"
WOODPECKER_ADMIN: "admin"
ingress:
enabled: true
ingressClassName: traefik
hosts:
- host: stonks-ci.celestium.life
paths:
- path: /
backend:
serviceName: woodpecker-server
servicePort: 80
tls:
- secretName: woodpecker-tls
hosts:
- stonks-ci.celestium.life
annotations:
cert-manager.io/cluster-issuer: ca-issuer
persistentVolume:
enabled: true
size: 5Gi
storageClass: ""
agent:
enabled: true
replicaCount: 2
env:
WOODPECKER_SERVER: "woodpecker-server:9000"
WOODPECKER_BACKEND: kubernetes
WOODPECKER_BACKEND_K8S_NAMESPACE: woodpecker
WOODPECKER_BACKEND_K8S_VOLUME_SIZE: 10G
WOODPECKER_BACKEND_K8S_STORAGE_RWX: "true"
Network Policy:
A NetworkPolicy in the woodpecker namespace allows Traefik ingress traffic to the Woodpecker server on its HTTP port (80).
3. Woodpecker Pipeline File (.woodpecker.yml)
The pipeline file translates the existing GitHub Actions workflow into Woodpecker's native format. Each step runs as a Docker container.
Pipeline Structure:
.woodpecker.yml
├── lint-python (ruff check services/)
├── test-python (pytest tests/)
├── test-frontend (npm ci && npx vitest --run)
├── build-<service> (×12 Python services, sequential or grouped)
├── build-dashboard (frontend/Dockerfile)
├── build-superset (docker/Dockerfile.superset)
├── integration-test (run_pipeline.sh)
└── mirror-github (git push to GitHub)
Key Differences from GitHub Actions:
- No
uses:syntax — each step specifies animage:andcommands:or uses a Woodpecker plugin - Image builds use
woodpeckerci/plugin-docker-buildxplugin withsettings.repo,settings.registry,settings.tags - Branch filtering via
when: { branch: main, event: push }instead of GitHub'sif:conditions - Secrets referenced via
from_secret:instead of${{ secrets.X }} - No matrix builds in Woodpecker — services are built sequentially or via multiple steps
Image Tagging:
All images pushed to registry.celestium.life/stonks-oracle/<service>:<sha> and registry.celestium.life/stonks-oracle/<service>:latest.
GitHub Mirror Step:
Uses the woodpeckerci/plugin-git-push plugin or a custom step with git push --mirror using an SSH deploy key stored as a Woodpecker secret.
4. ArgoCD Updates
Repo Secret Update (pipelines/argocd/repo-secret.yaml):
Change the repository URL from GitHub to Gitea:
stringData:
url: http://gitea-http.git-server.svc.cluster.local:3000/admin/stonks-oracle.git
type: git
username: admin
password: <gitea-admin-password>
Application Updates (pipelines/argocd/apps/*.yaml):
All three Applications (stonks-beta, stonks-paper, stonks-live) update spec.source.repoURL from https://github.com/celesrenata/stonks-oracle.git to the Gitea repository URL.
5. Kargo Warehouse Update
Warehouse Update (pipelines/kargo/warehouse.yaml):
Change the image subscription from GHCR to the local registry:
spec:
subscriptions:
- image:
repoURL: registry.celestium.life/stonks-oracle/query-api
Kargo stages, project, project-config, and market-hours AnalysisTemplate remain unchanged.
6. Helm Chart Updates (infra/helm/stonks-oracle/)
values.yaml changes:
image:
registry: registry.celestium.life/stonks-oracle # was: ghcr.io/celesrenata/stonks-oracle
pullPolicy: Always
tag: latest
# REMOVED: imagePullSecrets, ghcrAuth sections
values-beta.yaml and values-paper.yaml:
No changes needed — they inherit image.registry from the base values.yaml and only override image.tag.
7. ARC Teardown
The runmefirst.sh script tears down ARC before installing Woodpecker:
helm uninstall arc-runner-set --namespace arc-system || truehelm uninstall arc --namespace arc-system || truekubectl delete -f arc/runner-rbac.yaml --ignore-not-foundkubectl delete pv pipeline-arc-pv --ignore-not-foundkubectl delete namespace arc-system --ignore-not-found
The pipelines/arc/ directory and pipelines/pvs/arc-pv.yaml are removed from the repo.
8. NFS Persistent Volumes
Updated PV set (ARC PV removed, Woodpecker PV added):
| PV Name | NFS Path | Capacity | Bound To |
|---|---|---|---|
pipeline-argocd-pv |
/volume1/Kubernetes/pipelines/argocd |
5Gi | PVC in argocd ns |
pipeline-kargo-pv |
/volume1/Kubernetes/pipelines/kargo |
2Gi | PVC in kargo ns |
pipeline-woodpecker-pv |
/volume1/Kubernetes/pipelines/woodpecker |
5Gi | PVC in woodpecker ns |
9. Updated runmefirst.sh
#!/bin/bash
set -euo pipefail
# 1. Tear down ARC (if present)
# - Uninstall ARC Helm releases
# - Delete RBAC, PV, namespace
# 2. Create namespaces (woodpecker, argocd, kargo, stonks-beta, stonks-paper)
# 3. Create NFS PVs (argocd, kargo, woodpecker)
# 4. Configure Gitea
# - Complete initial setup via API
# - Create admin user (if needed)
# - Create OAuth2 app for Woodpecker
# - Create stonks-oracle repository
# 5. Install Woodpecker CI via Helm
# - Inject Gitea OAuth2 client_id and client_secret into values
# - Apply NetworkPolicy for Traefik ingress
# 6. Install ArgoCD via Helm
# - Apply updated repo secret (pointing to Gitea)
# - Apply ArgoCD Applications
# 7. Install Kargo via Helm
# - Apply project, project-config, warehouse (local registry), stages
# 8. Apply Woodpecker agent RBAC for integration tests
10. Updated runmelast.sh
#!/bin/bash
set -euo pipefail
# Reverse order: Kargo → ArgoCD → Woodpecker
# Preserves: PVs, NFS data, git-server namespace (Gitea + registry)
# 1. Remove Kargo resources + Helm release
# 2. Remove ArgoCD resources + Helm release
# 3. Remove Woodpecker Helm release
# 4. Delete namespaces (woodpecker, argocd, kargo)
# 5. PVs intentionally NOT deleted
11. Woodpecker Agent RBAC
The Woodpecker agent's service account needs:
- Namespace-scoped RBAC (auto-created by Helm chart): Create/delete Pods, Services, PVCs in the
woodpeckernamespace for pipeline step execution. - ClusterRoleBinding (manually applied): Grant the agent service account
cluster-adminfor integration test steps that create ephemeral namespaces and deploy sandbox infrastructure. This mirrors the existing ARC runner RBAC pattern.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: woodpecker-agent-inttest
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: ServiceAccount
name: woodpecker-agent
namespace: woodpecker
Data Models
Pipeline Infrastructure Layout
~/sources/kube/pipelines/
├── runmefirst.sh # Full install: ARC teardown → Gitea config → Woodpecker → ArgoCD → Kargo
├── runmelast.sh # Teardown: Kargo → ArgoCD → Woodpecker (preserves PVs, git-server)
├── gitea/
│ └── setup.sh # Gitea API setup: admin user, OAuth2 app, repo creation
├── woodpecker/
│ ├── values.yaml # Woodpecker Helm values (server + agent)
│ └── network-policy.yaml # NetworkPolicy for Traefik → Woodpecker server
│ └── agent-rbac.yaml # ClusterRoleBinding for integration test access
├── argocd/
│ ├── values.yaml # ArgoCD Helm values (unchanged)
│ ├── repo-secret.yaml # Updated: points to Gitea instead of GitHub
│ └── apps/
│ ├── stonks-beta.yaml # Updated: repoURL → Gitea
│ ├── stonks-paper.yaml # Updated: repoURL → Gitea
│ └── stonks-live.yaml # Updated: repoURL → Gitea
├── kargo/
│ ├── values.yaml # Kargo Helm values (unchanged)
│ ├── project.yaml # Kargo Project (unchanged)
│ ├── project-config.yaml # Kargo ProjectConfig (unchanged)
│ ├── warehouse.yaml # Updated: watches local registry
│ ├── market-hours-check.yaml # AnalysisTemplate (unchanged)
│ └── stages/
│ ├── beta.yaml # Kargo Stage (unchanged)
│ ├── paper.yaml # Kargo Stage (unchanged)
│ └── live.yaml # Kargo Stage (unchanged)
└── pvs/
├── argocd-pv.yaml # NFS PV for ArgoCD (unchanged)
├── kargo-pv.yaml # NFS PV for Kargo (unchanged)
└── woodpecker-pv.yaml # NFS PV for Woodpecker (NEW, replaces arc-pv.yaml)
Removed Files
pipelines/arc/ # Entire directory removed
├── values.yaml
├── runner-scaleset.yaml
└── runner-rbac.yaml
pipelines/pvs/arc-pv.yaml # ARC PV removed
Image Tag Flow (Updated)
Git SHA (e.g., abc123)
→ Woodpecker builds: registry.celestium.life/stonks-oracle/<service>:abc123
→ Integration test: run_pipeline.sh --image-tag abc123
→ GitHub mirror: git push (non-blocking)
→ Kargo Warehouse detects: abc123 in local registry
→ Kargo Freight created: abc123
→ Beta: helm upgrade with image.tag=abc123
→ Paper: helm upgrade with image.tag=abc123 (after market-hours check)
→ Live: helm upgrade with image.tag=abc123 (after approval + market-hours check)
Kargo Resource Relationships (Updated)
graph TD
W[Warehouse: stonks-images<br/>watches LOCAL REGISTRY<br/>registry.celestium.life] -->|produces| F[Freight<br/>image tag = git SHA]
F -->|auto-promote| SB[Stage: beta<br/>ArgoCD App: stonks-beta]
SB -->|verified → available| SP[Stage: paper<br/>market-hours verification<br/>ArgoCD App: stonks-paper]
SP -->|verified → available| SL[Stage: live<br/>manual approval + market-hours<br/>ArgoCD App: stonks-live]
Error Handling
Gitea Setup Failures
| Failure | Detection | Recovery |
|---|---|---|
| Gitea not reachable | API call returns connection error | Check Gitea pod status in git-server namespace. Verify NodePort service. |
| Admin user already exists | API returns 422 | Script continues — idempotent. |
| OAuth2 app already exists | API returns 422 | Script queries existing apps and reuses credentials. |
| Repository already exists | API returns 409 | Script continues — idempotent. |
Woodpecker Deployment Failures
| Failure | Detection | Recovery |
|---|---|---|
| Helm install fails | Non-zero exit | Check Helm chart repo access. Verify woodpecker namespace exists. |
| Server can't reach Gitea | OAuth2 login fails | Verify WOODPECKER_GITEA_URL resolves within cluster. Check Gitea service. |
| Agent can't connect to server | Agent logs show connection errors | Verify WOODPECKER_SERVER env var matches server service name. Check agent secret. |
| Pipeline step Pod fails to schedule | Pod stuck in Pending | Check node resources. Verify RBAC allows Pod creation in woodpecker namespace. |
| Image build fails (privileged) | Build step exits non-zero | Verify containerd/k3s allows privileged Pods. Check plugin-docker-buildx logs. |
Pipeline Failures
| Failure | Detection | Recovery |
|---|---|---|
| Lint/test fails | Step exits non-zero | Fix code, push again. Build steps are skipped. |
| Image push to local registry fails | Plugin exits non-zero | Check registry health at registry.celestium.life. Verify DNS resolution. |
| Integration test fails | run_pipeline.sh exits non-zero |
Check Woodpecker dashboard for step logs. Fix and re-push. |
| GitHub mirror fails | Mirror step exits non-zero | Non-blocking — images are already in local registry. Fix SSH key and re-run. |
ArgoCD/Kargo Update Failures
| Failure | Detection | Recovery |
|---|---|---|
| ArgoCD can't clone from Gitea | Application shows "ComparisonError" | Verify repo secret credentials. Check Gitea accessibility from ArgoCD namespace. |
| Kargo can't reach local registry | Warehouse shows error | Verify registry.celestium.life DNS resolves. Check registry pod health. |
| Image pull fails (k3s nodes) | Pods stuck in ImagePullBackOff | Ensure k3s containerd trusts the local registry. Add registry mirror config if needed. |
Rollback Strategy
Same as existing design:
- Beta/Paper: Promote a previous Freight in Kargo to roll back the image tag.
- Live: Same mechanism with manual approval required.
- Emergency: Direct
helm upgradewith previous image tag.
Testing Strategy
Why Property-Based Testing Does Not Apply
This feature is entirely Infrastructure as Code: shell scripts, Kubernetes YAML manifests, Helm values files, and a Woodpecker pipeline YAML file. There are no pure functions, parsers, serializers, or business logic with meaningful input variation. PBT requires universal properties across a wide input space — this feature has fixed configuration values and Kubernetes resource states. Running 100 iterations of "does the Woodpecker ingress have TLS enabled" adds no value over running it once.
Testing Approach
The testing strategy uses three tiers:
Tier 1: Smoke Tests (Configuration Validation)
Run locally or in CI without a live cluster.
| Test | What It Validates | How |
|---|---|---|
| Manifest syntax | All YAML files parse correctly | kubectl apply --dry-run=client -f <file> |
| Helm template rendering | Woodpecker values produce valid K8s resources | helm template with values file |
| Pipeline file syntax | .woodpecker.yml is valid |
Woodpecker CLI lint or YAML parse |
| Namespace isolation | Pipeline namespaces distinct from stonks-oracle and git-server |
Grep manifests for namespace fields |
| NFS path separation | PVs use distinct subdirectories | Inspect PV YAML |
| Image registry references | All manifests reference registry.celestium.life not ghcr.io |
Grep all YAML for registry URLs |
| No GHCR auth remnants | ghcrAuth and ghcr-credentials removed from Helm chart |
Grep values.yaml |
| ArgoCD repo URL | All Applications point to Gitea, not GitHub | Inspect Application YAML |
| Kargo warehouse URL | Warehouse watches local registry | Inspect warehouse YAML |
Tier 2: Integration Tests (Live Cluster Verification)
Run after runmefirst.sh on the Gremlin cluster.
| Test | What It Validates | How |
|---|---|---|
| Gitea accessible | Web UI responds | curl http://10.1.1.x:30300 |
| Gitea repo exists | stonks-oracle repo created |
Gitea API query |
| Woodpecker server running | Pods healthy in woodpecker namespace |
kubectl get pods -n woodpecker |
| Woodpecker dashboard accessible | Web UI responds at stonks-ci.celestium.life |
curl -k https://stonks-ci.celestium.life |
| Woodpecker OAuth2 works | Login redirects to Gitea | Browser test |
| ArgoCD accessible | Web UI responds at stonks-argocd.celestium.life |
curl -k https://stonks-argocd.celestium.life |
| ArgoCD syncs from Gitea | Applications sync successfully | argocd app get stonks-beta |
| Kargo Warehouse | Discovers images from local registry | kubectl get freight -n stonks-oracle |
| Local registry accessible | Registry responds | curl https://registry.celestium.life/v2/_catalog |
| TLS certificates | Ingresses have valid certs from ca-issuer |
openssl s_client or cert-manager status |
| PV binding | PVCs bound to NFS PVs | kubectl get pvc -n woodpecker |
| ARC removed | No ARC pods, no arc-system namespace |
kubectl get ns arc-system returns NotFound |
| End-to-end pipeline | Push triggers build, images land in local registry | Push a commit, verify in Woodpecker dashboard |
| End-to-end promotion | Image flows beta → paper → live | Trigger promotion, verify deployments update |
| Teardown preservation | After runmelast.sh, PVs and NFS data intact |
Run teardown, check PVs and NFS mount |
Tier 3: Market-Hours and Break-Glass Tests
Unchanged from existing design — these tests validate Kargo behavior which is not modified.
| Test | What It Validates | How |
|---|---|---|
| Market-hours block | Promotion blocked during 09:30–16:00 ET | Run AnalysisTemplate during market hours |
| Market-hours allow | Promotion allowed outside hours | Run AnalysisTemplate outside hours |
| Break-glass override | Manual approval bypasses block | Use Kargo manual approval during hours |
| Break-glass audit | Records operator, timestamp, justification | Query Kargo audit trail |