docs(ci): sharpen CI/CD spec to shared-engine/separate-projects principle

Operator's principle: fxhnt and bizworx are different projects — use the
shared bizworx platform infra (Gitea, Argo engine, registry, node-pools)
but keep fxhnt-owned objects (WorkflowTemplate, build/deploy pods, secrets,
RBAC) in the foxhunt ns. Only the Sensor lives in argo-events (unavoidable,
kept minimal). No cross-ns RBAC, no fxhnt secrets in platform ns. Adds a
deployed-sha annotation for git-sync reproducibility.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-21 10:03:28 +00:00
parent 5d15b0f801
commit fc8b1b7849

View File

@@ -13,9 +13,28 @@ fast rollout (git-sync re-pulls src); only dependency/Dockerfile changes trigger
The operator's insight: **code changes often, the image changes rarely.** git-sync already pulls fresh `src/`
on every (re)start, so a code-only push needs only a rollout — not a 5-minute Kaniko build. A rebuild is needed
only when `pyproject.toml` / `fxhnt.Dockerfile` / lock change. This is exactly the `mode=build` vs `mode=deploy`
split the retired `infra/argo/cockpit-build-deploy.yaml` already encoded — we reanimate and fix it, rather than
adopt the generic `build-image` template (which lacks the split and fxhnt's deploy specifics).
only when `pyproject.toml` / `fxhnt.Dockerfile` change. This is exactly the `mode=build` vs `mode=deploy` split
the retired `infra/argo/cockpit-build-deploy.yaml` already encoded — we reanimate and fix it, rather than adopt
the generic `build-image` template (which lacks the split and fxhnt's deploy specifics).
## Governing principle: shared engine, separate projects
fxhnt and bizworx are **different projects**. fxhnt should *use* bizworx's heavy shared platform infra but not
*pollute* the platform namespaces with fxhnt-specific objects or secrets:
- **SHARE (bizworx platform, use as-is):** the one Gitea (webhook source), the Argo Events eventbus +
`gitea-webhook` eventsource, the Argo Workflows controller (the build *engine*), external-secrets/openbao,
the `large` node-pool, the Scaleway registry.
- **SEPARATE (fxhnt-owned, live in `foxhunt`):** the `WorkflowTemplate`, all build+deploy pods, fxhnt's
secrets (`argo-git-ssh-key`, `scw-registry` — already in `foxhunt`), and fxhnt's RBAC.
- **The ONE unavoidable object in a platform ns:** the Sensor. A Sensor must live in `argo-events` to hear the
shared eventbus. Keep its footprint minimal — it only matches fxhnt pushes and *submits* a Workflow that runs
in `foxhunt`. No fxhnt secrets, no fxhnt logic beyond the trigger, live in `argo-events`.
This is NOT the old "everything in foxhunt because the secrets were there" (path of least resistance) — it's the
same placement for the *right* reason: respecting the project boundary. It also means **no cross-namespace RBAC**
(the deploy runs in `foxhunt`, where the app + its permissions already are) and **no fxhnt secrets replicated
into `argo`**.
## Verified current state (all components already exist on bizworx)
@@ -36,31 +55,36 @@ adopt the generic `build-image` template (which lacks the split and fxhnt's depl
## Architecture
```
git push → gitadmin/fxhnt (master)
│ Gitea webhook ──────────────► argo-events `gitea-webhook` eventsource (/push)
git push → gitadmin/fxhnt (master) [SHARED: Gitea]
│ Gitea webhook ──────────────► argo-events `gitea-webhook` eventsource [SHARED: platform eventbus]
Sensor `build-fxhnt` — filters: ref==refs/heads/master, pusher != argocd-image-updater
Sensor `build-fxhnt` (ns argo-events — the ONE fxhnt object in a platform ns, kept minimal)
│ filters: ref==refs/heads/master, pusher != argocd-image-updater
│ path-filter on the pushed commits' modified files decides the mode:
│ • touched pyproject.toml OR infra/docker/fxhnt.Dockerfile → param mode=build
│ • otherwise (src/, infra/k8s/, config) → param mode=deploy
│ (NB: the repo has NO lock file — the Dockerfile `pip install`s straight from pyproject.toml,
│ so pyproject.toml + the Dockerfile are the ONLY dependency sources that require a rebuild.)
Argo Workflow (WorkflowTemplate `fxhnt-cockpit`, reanimated) — DAG:
├─ mode=build: git-clone(sha) → Kaniko build → rg.fr-par.scw.cloud/bizworx/fxhnt-cockpit:<sha7>+:latest
submits a Workflow into ns `foxhunt` ─────────────────────────────── [FXHNT-OWNED from here down]
Argo Workflow (WorkflowTemplate `fxhnt-cockpit`, ns foxhunt, SA argo-workflow, node-pool `large`) — DAG:
├─ mode=build: git-clone(sha) → Kaniko → rg.fr-par.scw.cloud/bizworx/fxhnt-cockpit:<sha7>+:latest
└─ (build skipped when mode=deploy)
→ deploy step: kubectl apply manifests + refresh the daemons (git-sync re-pulls src)
→ deploy step (in foxhunt, no cross-ns RBAC): kubectl apply manifests + refresh daemons (git-sync re-pulls src)
```
## Components to build/fix
### 1. WorkflowTemplate `fxhnt-cockpit` (reanimate `infra/argo/cockpit-build-deploy.yaml`)
- Fix `nodeSelector` `ci-compile-cpu``large` (both build + deploy templates).
### 1. WorkflowTemplate `fxhnt-cockpit` (reanimate `infra/argo/cockpit-build-deploy.yaml`, ns `foxhunt`)
- Fix `nodeSelector` `ci-compile-cpu``large` (both build + deploy templates`ci-compile-cpu` pool is gone;
only `large`/`sfs` exist).
- Keep the `mode` DAG: `build` (Kaniko rebuild + deploy) vs `deploy` (skip build, deploy only).
- Keep the Kaniko dest `rg.fr-par.scw.cloud/bizworx/fxhnt-cockpit` but ADD an immutable `:<sha7>` tag next to
`:latest` (reproducibility — image == commit; matches the msp pattern). Cache stays on.
- Clone via the in-cluster Gitea (`ssh://…gitea…/gitadmin/fxhnt.git`) using `argo-git-ssh-key`; push via
`scw-registry`. Verify both secrets resolve from the namespace the workflow runs in.
`:latest` (matches the msp pattern). Cache stays on.
- Clone via the in-cluster Gitea using `argo-git-ssh-key`; push via `scw-registry`. Both already exist in
`foxhunt` — the workflow runs there, so they resolve locally (no replication into a platform ns).
- **Reproducibility despite git-sync:** the deploy step stamps the deployed commit sha onto the daemon
Deployments as an annotation (`fxhnt.io/deployed-sha=<sha7>`), so "which commit is this pod running" is always
answerable — closing the one real gap of git-sync-first without adopting full image-based deploys.
### 2. Sensor `build-fxhnt` (new, model on `build-msp-portal`)
- Dependency: `gitea-webhook` push, filter `body.ref == refs/heads/master` AND
@@ -87,11 +111,16 @@ succeeded cleanly. **The plan must verify which external route is live** before
simpler and correct; drop the scale-dance.
Dagster has no external-proxy race → `rollout restart deploy/dagster` either way.
## Non-goals (YAGNI)
## Non-goals (YAGNI) — deliberately NOT doing, with the reason
- **No ArgoCD GitOps app for fxhnt.** That would force fxhnt's manifests into the bizworx monorepo. git-sync +
the mode-split deliver auto-deploy while keeping manifests in the fxhnt repo. A declarative ArgoCD migration
is a separate, later decision.
- **No ArgoCD GitOps app for fxhnt.** Chosen over declarative GitOps: it would add a second source of truth for
the pod's src (ArgoCD-synced manifests vs git-sync'd code) and needs a git.fxhnt.ai repo registration. The
imperative `kubectl apply` from the foxhunt workflow fits git-sync-first and keeps manifests in the fxhnt repo.
- **No registry unification.** fxhnt stays on Scaleway `rg.fr-par.scw.cloud/bizworx` (not the shared
`registry.bizworx.nl/bizworx-ops`). Migrating would mean re-pointing the dashboard + reconciling old images
for zero pipeline benefit — a separate, riskier task.
- **No CI workloads in a platform namespace.** Only the Sensor lives in `argo-events` (unavoidable). The
WorkflowTemplate + all build/deploy pods run in `foxhunt` — no fxhnt secrets in `argo`, no cross-ns RBAC.
- **No image-updater for fxhnt.** The sensor writes the `:sha7`+`:latest` tags; the deploy step points the
daemons at the fresh image. No git-write-back loop needed.
- **No change to the execution CronJobs** (ucits/bybit/ibkr rebalancers) — they git-sync per run already.