Operator addition to the closure protocol: the QA gate is held by an independent verifier subagent — context-free, prompted to REFUTE, repo + issues + journals as its only inputs; verdict posted on the milestone, unresolved refutation blocks. New qa-strategy section extends no-self- grading to POC exit gates and autonomy promotions (verdict attached to the artifact it gates), mirroring at process level what the pipelines do at data level (dual extraction, seeded-wrong fixtures). Co-Authored-By: Claude Fable 5 <[email protected]>
137 lines
13 KiB
Markdown
137 lines
13 KiB
Markdown
# Arcodange Lab — Agent Guide & Operating Rules
|
|
|
|
The Arcodange lab is a self-hosted home/company platform running on three Raspberry Pi (pi1/pi2/pi3) behind a home Livebox, driven from a MacBook Pro M4 control node. **factory** is the cornerstone admin repo: it provisions the cluster (k3s), defines what gets deployed (ArgoCD app-of-apps), manages cloud/forge state (OpenTofu), and provisions databases (PostgreSQL). Two sibling repos carry workloads: **tools** (platform services — Vault, Prometheus, Grafana, CrowdSec, poolers) and **cms** (the public Nuxt site `arcodange.fr`). Everything is deployed into k3s namespaces via ArgoCD, every secret comes from Vault, and public traffic enters through a Cloudflared Zero-Trust tunnel into the internal Traefik.
|
|
|
|
```mermaid
|
|
%%{init: {'theme':'base'}}%%
|
|
flowchart TB
|
|
subgraph control["Control node (MacBook Pro M4)"]
|
|
ansible["Ansible"]:::proc
|
|
tofu["OpenTofu"]:::proc
|
|
end
|
|
|
|
factory["factory repo<br>orchestrator: ansible + argocd + iac + postgres + doc"]:::src
|
|
tools["tools repo<br>Vault, Prometheus, Grafana, CrowdSec, poolers"]:::src
|
|
cms["cms repo<br>Nuxt site arcodange.fr"]:::src
|
|
|
|
subgraph cluster["k3s cluster (pi1 server, pi2/pi3 agents)"]
|
|
argocd["ArgoCD app-of-apps"]:::proc
|
|
vault["Vault + VSO"]:::store
|
|
nsapps["namespaces: tools / cms / webapp / erp / ..."]:::proc
|
|
traefik["internal Traefik"]:::proc
|
|
end
|
|
|
|
cflared["Cloudflared Zero-Trust tunnel"]:::proc
|
|
public["public: *.arcodange.fr"]:::store
|
|
|
|
ansible -- "provision k3s + base" --> cluster
|
|
tofu -- "state in GCS" --> factory
|
|
factory -- "defines Applications" --> argocd
|
|
argocd -- "deploy charts" --> tools
|
|
argocd -- "deploy charts" --> cms
|
|
tools --> nsapps
|
|
cms --> nsapps
|
|
vault -- "inject secrets" --> nsapps
|
|
nsapps --> traefik
|
|
cflared -- "ingress" --> traefik
|
|
public --> cflared
|
|
|
|
classDef src fill:#2563eb,stroke:#1e40af,color:#fff
|
|
classDef proc fill:#059669,stroke:#047857,color:#fff
|
|
classDef store fill:#7c3aed,stroke:#6d28d9,color:#fff
|
|
```
|
|
|
|
1. The **control node** (MacBook Pro M4) runs Ansible and OpenTofu — the two hands that build everything.
|
|
2. **Ansible** provisions the **k3s cluster** (pi1 server, pi2/pi3 agents) and its base layer.
|
|
3. **OpenTofu** manages forge/cloud state, persisted in **GCS** (`gs://arcodange-tf`), serving the **factory** repo's intent.
|
|
4. **factory** defines the **ArgoCD app-of-apps**, which is the single deployment authority.
|
|
5. **ArgoCD** deploys the Helm charts of **tools** and **cms** into per-app k3s **namespaces**.
|
|
6. **Vault** (with the Vault Secrets Operator) injects secrets into those namespace pods — no secrets live in git.
|
|
7. Workload pods route through the **internal Traefik**.
|
|
8. The **Cloudflared Zero-Trust tunnel** is the only public ingress, forwarding `*.arcodange.fr` traffic into internal Traefik.
|
|
|
|
---
|
|
|
|
## Repos at a glance
|
|
|
|
| Repo | Purpose | Key dirs | How deployed |
|
|
|---|---|---|---|
|
|
| **factory** | Cornerstone admin repo: provisions the cluster, defines deployments, owns infra state and DBs, holds canonical docs | [ansible/](ansible/) · [argocd/](argocd/) · [iac/](iac/) · [postgres/](postgres/) · [doc/](doc/) | Ansible (cluster + base) + OpenTofu (forge/cloud/PG state); ArgoCD reads its app-of-apps |
|
|
| **tools** | Platform services in the `tools` namespace | [hashicorp-vault](https://gitea.arcodange.lab/arcodange-org/tools/src/branch/main/hashicorp-vault), [prometheus](https://gitea.arcodange.lab/arcodange-org/tools/src/branch/main/prometheus), [grafana](https://gitea.arcodange.lab/arcodange-org/tools/src/branch/main/grafana), [crowdsec](https://gitea.arcodange.lab/arcodange-org/tools/src/branch/main/crowdsec), [pgbouncer](https://gitea.arcodange.lab/arcodange-org/tools/src/branch/main/pgbouncer) | Helm/Kustomize charts deployed via ArgoCD |
|
|
| **cms** | Public Nuxt static site `arcodange.fr` + its zone/email IaC | [chart](https://gitea.arcodange.lab/arcodange-org/cms/src/branch/main/chart), [cloudflare](https://gitea.arcodange.lab/arcodange-org/cms/src/branch/main/cloudflare), [zoho](https://gitea.arcodange.lab/arcodange-org/cms/src/branch/main/zoho) | Helm chart via ArgoCD; Cloudflare/Zoho via OpenTofu |
|
|
|
|
Self-hosted Gitea is at `gitea.arcodange.lab` (org `arcodange-org`). **Pick the forge tool from the remote**: these repos live on Gitea, so use the `mcp__gitea__*` MCP tools for PRs/issues/releases — `gh` will silently fail.
|
|
|
|
## The `<app>` join key
|
|
|
|
One kebab-case identifier — `<app>` — is reused **identically** across the Gitea repo, the PG database + `<app>_role`, Vault (`postgres/creds/<app>`, k8s auth role `<app>`, policies `<app>` / `<app>-ops`, `gitea_cicd_<app>`), the k8s namespace + ServiceAccount, the ArgoCD Application, the GCS state prefix `<app>/main`, and DNS (`<app>.arcodange.lab` / `<app>.arcodange.fr`). Bricks wire together **by name convention, not explicit config**, so a single typo breaks the chain silently. Source of truth: [doc/runbooks/new-web-app/conventions.md](doc/runbooks/new-web-app/conventions.md); concept page (going forward): [vibe/guidebooks/lab-ecosystem/naming-conventions.md](vibe/guidebooks/lab-ecosystem/naming-conventions.md).
|
|
|
|
## Where knowledge lives
|
|
|
|
Start at the knowledge-base front door: [vibe/README.md](vibe/README.md). The six `vibe/` folders:
|
|
|
|
- [vibe/ADR/](vibe/ADR/README.md) — architecture decision records (the *why*); canonical home going forward.
|
|
- [vibe/PRD/](vibe/PRD/README.md) — product/project requirement docs, each with a mandatory `STATUS.md`.
|
|
- [vibe/investigations/](vibe/investigations/README.md) — numbered investigations (`INV-NNN-slug`), with notebooks when data-heavy.
|
|
- [vibe/guidebooks/](vibe/guidebooks/README.md) — tree-docs that map the lab's components (the *how it fits together*).
|
|
- [vibe/runbooks/](vibe/runbooks/README.md) — step-by-step operational procedures with `[AGENT]` / `[HUMAN]` markers.
|
|
- [vibe/shareouts/](vibe/shareouts/README.md) — handouts and presentations (FRENCH; the one exception to the English rule).
|
|
|
|
Historical infra docs still live under [doc/](doc/) (ADRs, the new-web-app runbook) — see also `CLUSTER_RECOVERY.md` (at the lab root, **outside** this repo) for tested power-cut recovery.
|
|
|
|
**Active backlog:** the [AI back-office PRD](vibe/PRD/ai-back-office/README.md) is decomposed into self-contained issues on dated milestones — entry point: its [STATUS backlog map](vibe/PRD/ai-back-office/STATUS.md). Resume protocol: pick the top unblocked issue of the earliest open [erp milestone](https://gitea.arcodange.lab/arcodange-org/erp/milestones); related: [telegram-gateway issues](https://gitea.arcodange.lab/arcodange/telegram-gateway/issues) (owner `arcodange`) and [factory#22](https://gitea.arcodange.lab/arcodange-org/factory/issues/22) (ADR tracking). The erp repo's own `AGENTS.md` carries the same pointers.
|
|
|
|
## Operating rules for agents
|
|
|
|
### No-tombstone rule (FOREMOST)
|
|
Write every file as **currently true**. NEVER leave "Correction (date): …", "previously X, now Y", changelogs, or "updated to …" notes — git history is the audit trail. The **only** allowed exception is a forward-looking `> [!CAUTION]` about a live operational risk.
|
|
|
|
### Mermaid preferences
|
|
Begin each block with an init directive selecting `theme base` (or `forest`). Define a `classDef` palette legible on both light and dark backgrounds (dark fills + light text), e.g. `classDef src fill:#2563eb,stroke:#1e40af,color:#fff`. Use HTML `<br>` for line breaks (never `\n`). Put a leading space before any label starting with a slash, and escape angle brackets inside labels. **Validate every diagram with the Mermaid MCP** before committing, and **immediately after each diagram add a numbered ordered list restating the same flow in words**.
|
|
|
|
### Tree-docs (guidebooks & big PRDs)
|
|
Guidebooks and large PRDs are written as navigable trees: every file's **first line is a breadcrumb** (ancestors are relative links, current page is bold-unlinked, separator ` > `); **every folder has a `README.md` index hub** (a table of its children — link + one-line summary + status — sorted by importance/sequence, not alphabetically); **cross-references are bidirectional** (if A links B, B links A); use numbered file prefixes only for ordered narratives; **stamp `Last Updated: 2026-06-23` at each tree root**.
|
|
|
|
### Optimized ADR format (MADR-lite)
|
|
Sections: **Context / Decision / Consequences / Alternatives / QA & validation / References**. Once an ADR is **Accepted, the body is immutable** — only the status field mutates (Proposed → Accepted → Superseded). The canonical home going forward is [vibe/ADR/](vibe/ADR/README.md); [doc/adr/](doc/adr/README.md) stays as the historical record.
|
|
|
|
### Investigations
|
|
Prefer a **single `INV-NNN-slug.md`** when the finding fits in one file. When data-heavy, write a short stub `.md` beside a same-named folder containing notebooks (`.ipynb` + paired `.py`), a `_data/` dir, and a plain-language `notebook_simple.md` (visuals anyone can read).
|
|
|
|
### PRD convention
|
|
**One subfolder per PRD.** A `STATUS.md` is **mandatory** and must be updated whenever something ships. Big PRDs use tree-docs and **must detail a QA strategy**. The flow is Problem → … → QA strategy → STATUS.md.
|
|
|
|
### PR crosslinking (bidirectional)
|
|
**Every PR body references the ADR/PRD it advances.** In return, the ADR's **References** section and the PRD's **STATUS.md** link back to that PR. Links must be bidirectional — never one-way.
|
|
|
|
### Guidebook maintenance
|
|
Altering a component that is documented in `guidebooks/` **requires updating that guidebook page in the same change**. A code/infra change that leaves its guidebook stale is incomplete.
|
|
|
|
### Milestone closure & doc freshness
|
|
Docs describe intent; **STATUS.md + git describe reality**. Writer's half: a milestone is closed only after the closure sweep, which **opens with a QA gate run by an independent, context-free verifier subagent prompted to refute** — the closer never self-certifies (acceptance criteria re-verified with evidence, test suites green, status flips backed by eval results — nothing documented as done before it is proven done) and **ends with doc-surface QA** (link/anchor check, deprecation grep for the claims the milestone retired, fresh-reader smoke test by a context-free subagent); in between: flip the PRD `STATUS.md` phase row, re-baseline the roadmap at the boundary, truth-pass every leaf/`AGENTS.md`/`SKILL.md` claim the increment invalidated (no-tombstone, bump Last Updated on changed files only). Canonical checklist: the [ai-back-office STATUS closure protocol](vibe/PRD/ai-back-office/STATUS.md). Continuously: a PR that makes a documented claim false updates that doc **in the same PR**. Reader's half: before acting on any versionable claim, verify in trust order — **live system > code/git log > STATUS > leaves > memories/plans**; a page whose Last Updated predates the newest closed milestone in its area is suspect.
|
|
|
|
### Language policy
|
|
**English** for everything in `vibe/` and for `AGENTS.md`/`CLAUDE.md` (this tree is for LLM agents). The single exception: **shareouts handouts are FRENCH**.
|
|
|
|
## The cohort + workflow
|
|
|
|
These personas are spawned via the Agent tool or a Workflow; document and reuse them across sessions:
|
|
|
|
| Persona | Role |
|
|
|---|---|
|
|
| **Lab Cartographer** | Explores the three repos and maps them into guidebooks (tree-docs). Read-mostly; never edits infra. |
|
|
| **ADR Scribe** | Writes optimized MADR-lite ADRs; enforces immutability (status-only mutation) and PR crosslinks. |
|
|
| **PRD Architect** | Writes PRDs (Problem → … → QA strategy → STATUS.md); uses tree-docs for big ones. |
|
|
| **Runbook Engineer** | Writes step-by-step runbooks with `[AGENT]` (read-only, safe) and `[HUMAN]` (prod-mutating, needs approval) markers. |
|
|
| **Investigator** | Writes investigations: a single `INV-NNN-slug.md` when possible, else a stub `.md` beside a same-named folder with `.ipynb` + paired `.py`, a `_data/` dir, and `notebook_simple.md`. |
|
|
| **Diagram Smith** | Authors and validates every mermaid diagram with the Mermaid MCP validator; enforces the mermaid preferences + the ordered-list-after-diagram rule. |
|
|
| **Continuity Warden** | The adversarial reviewer: checks the no-tombstone rule, breadcrumb depth, bidirectional links, dead links, naming, STATUS/PR crosslinks, and the Last Updated stamp. |
|
|
|
|
**Recommended workflow for substantial `vibe/` contributions:**
|
|
|
|
1. **Scaffold** — folders + README hubs + templates.
|
|
2. **Author** — personas in parallel, each on distinct files.
|
|
3. **Validate** — Diagram Smith runs the Mermaid MCP on every diagram.
|
|
4. **Review** — Continuity Warden runs the adversarial checklist.
|
|
5. **Assemble** — wire cross-refs, run the dead-link self-test, stamp `Last Updated`.
|