L'ADR annonçait que les noms d'actions MinIO de la politique du provisionneur
venaient de la documentation, pas d'un essai. Le premier apply (kadans,
2026-07-26) a répondu : tout le bloc admin passe, il manquait `s3:ListBucket`
côté S3 — le provider interroge l'existence du bucket avant de le créer.
La conséquence devient un constat, avec ce que ListBucket concède (la vue des
clés) et ce qu'il ne concède pas (leur contenu).
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01CoafGWmRVESaWX819USUUA
Trois questions indépendantes, tranchées lors du branchement de Kadans sur
MinIO (2026-07-26) : qui déclare les buckets d'une app, qui détient les
identifiants capables de les créer, et comment l'app lit les siens.
La décision de fond est du fondateur : CHACUN SON PÉRIMÈTRE. Une application
déclare ses buckets depuis son propre dépôt ; `tools` fournit le serveur, un
module de standardisation et un compte de provisionnement — pas la liste. Une
première version faisait tout porter par l'infra partagée : à ce rythme, chaque
bucket de chaque app devenait une PR sur le dépôt commun.
L'ADR consigne aussi les trois identités et leurs portées (root / provisionneur
/ compte de service), pourquoi la lecture des identifiants est une propriété
inconditionnelle de la plateforme plutôt qu'une déclaration par app, et pourquoi
les octets ne transitent pas par l'API — avec les conséquences que ça impose
(endpoint public, CORS aux origines exactes, pas de basic-auth sur l'ingress S3).
Les alternatives écartées sont listées avec leur motif, dont deux que j'avais
moi-même proposées et qui étaient plus faibles.
Deux limites assumées y figurent : le provisionneur est un secret PARTAGÉ entre
rôles CI (sa compromission permet de créer des buckets, pas de lire des objets),
et les noms d'actions d'administration MinIO n'ont pas été éprouvés contre le
serveur au moment d'écrire.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01CoafGWmRVESaWX819USUUA
L'app url-shortener était en SyncError permanent : son PVC live porte un
spec.volumeName épinglé (rebind du volume Longhorn après le drill coupure de
courant) absent du chart ; chaque sync tentait donc de le vider, refus API
(spec immuable après création), échec en boucle malgré automated+selfHeal.
- apps.yaml : passthrough générique ignoreDifferences + syncOptions par app.
- values.yaml : url-shortener ignore /spec/volumeName du PVC, avec
RespectIgnoreDifferences=true pour que l'apply réinjecte la valeur live au
lieu de la vider (le cas d'usage documenté d'ArgoCD pour les champs
immuables).
Rendu helm vérifié : seule l'Application url-shortener change.
Co-Authored-By: Claude Fable 5 <[email protected]>
Le chapitre « service compagnon » montrait `vaultConnectionRef: default` dans
l'exemple VaultAuth — c'est faux hors du namespace `tools` et ça a réellement bloqué
le déploiement de kadans-api (pods en CreateContainerConfigError, VaultDynamicSecret
sur « VaultConnection default not found »).
VSO résout vaultConnectionRef dans le namespace DU CR ; la VaultConnection `default`
ne vit que dans `tools`. Les apps hors `tools` (erp, webapp) OMETTENT le champ et
laissent VSO retomber sur sa defaultVaultConnection. On retire donc la ligne de
l'exemple + on ajoute un encart WARNING dédié au piège.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_013ws8L74dVZmp97Wu36fm8j
Le runbook new-web-app couvre l'app autonome (dépôt/base/Vault/namespace propres,
tout nommé <app>). Il manquait le cas du SERVICE COMPAGNON : un second service qui
partage le namespace — et parfois le stack Vault/DB — d'une app existante (API cœur
à côté de son front, façade d'analyse). Deux précédents vivants non documentés :
kadans-jobs (namespace seul) et kadans-api (namespace + base + Vault).
- Nouvelle page 09-service-compagnon.md : compagnon vs app autonome ; les deux
formes (sans état / partage Vault+DB) ; le PIÈGE du VaultAuth manquant quand
l'app primaire ne consomme pas Vault (front statique) → le compagnon pose son
propre VaultAuth mais avec le rôle+SA du PRIMAIRE ; carte, précédents, delta de
checklist.
- 07-argocd-register.md : ajoute la ligne `namespace:` aux options (elle existait
dans values.yaml — kadans-jobs — mais n'était pas documentée) ; corrige le
callout qui affirmait le namespace « non configurable ».
- conventions.md : note l'exception compagnon à la règle « tout est <app> ».
- README.md : entrée 09 dans l'index + Last Updated.
Vérifié : VaultAuth erp nommé `auth` ; connexion via pgbouncer.tools ; liens
internes tous résolus.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_013ws8L74dVZmp97Wu36fm8j
Ajoute l'API cœur au registre gitea_applications. ArgoCD crée une Application
`kadans-api` (source arcodange/kadans-api, path chart, targetRevision HEAD), sync
automatique prune+selfHeal, image-updater par digest sur :latest — même moule que
les autres apps.
Namespace `kadans` (comme kadans-jobs) : kadans-api partage le stack Vault/DB déjà
en place pour l'app front (VaultAuth `kadans`, rôle Postgres dynamique
postgres/creds/kadans, ServiceAccount `kadans`, policy KV `kadans`). Aucun nouvel
iac/DB/Vault à provisionner.
À merger APRÈS le fix chart kadans-api (VaultAuth + hôte DB pgbouncer.tools) pour
que la première synchro ArgoCD parte d'un chart correct.
helm template rend l'Application kadans-api → repoURL arcodange/kadans-api,
namespace kadans, CreateNamespace, digest.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_013ws8L74dVZmp97Wu36fm8j
The never-yet-applied k3s_dns.yml placed 'import /etc/coredns/custom/*.server'
INSIDE the .:53 server block. *.server files hold full server blocks
(arcodange.lab:53 {…}), which only parse at Corefile root — inside a block
CoreDNS dies at startup with "Unknown directive 'arcodange.lab:53'"
(CrashLoopBackOff, cluster DNS fully down; lived it on 2026-07-24 while
restoring the expired *.arcodange.lab certificate).
Also restores the stock 'loadbalance' plugin dropped by the playbook.
Context: cluster CoreDNS forwarded to the node's resolv.conf, which lists the
ISP box's IPv6 RDNSS next to the Pi-holes — NXDOMAIN roulette for *.lab names.
That's what left step-issuer unable to reach ssl-ca.arcodange.lab:8443 and let
the 24h wildcard cert expire this morning. The (fixed) playbook pins .lab
resolution to the Pi-holes via the coredns-custom ConfigMap; applied live on
2026-07-24, wildcard renewed, strict TLS verified on gitea/argocd/grafana.
Co-Authored-By: Claude Fable 5 <[email protected]>
Incident 2026-07-23: an uncapped nuxt generate (3.5G RSS) on pi1 starved the
k3s control-plane and traefik (load >150, no swap, no OOM-kill) — every
*.arcodange.lab endpoint went dark, Gitea included, while Gitea itself was
healthy on pi2. Job containers are spawned via the host docker socket, so
cgroup caps on the job container are the only guardrail.
Applied live on pi1+pi3 via 03_cicd.yml on 2026-07-24 (both runners
re-registered; pi3's runner was down and is back in service).
Co-Authored-By: Claude Fable 5 <[email protected]>
kadans-jobs is the tier-2 (homelab, 24/7) piece of the Kadans topology: a job
queue plus the store of published analysis results, decoupling the product from
the volatile Mac worker.
org: arcodange — the repo does not live under the default arcodange-org.
Digest-based image-updater annotations follow the fleet pattern; the
cluster-wide ImageUpdater CR (namePattern "*", useAnnotations) picks them up,
so there is no per-app CR to maintain. The image is already in the registry.
No postgres/iac/terraform.tfvars entry, on purpose: the façade runs a memory
store in v0, so it needs neither a database nor Vault. That is the runbook's
degraded mode — the DB, the Vault JWT role and the app's own iac/ will land
together with the Postgres store.
Chart realigned on the runbook conventions first, in kadans-jobs#1.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_013ws8L74dVZmp97Wu36fm8j
Co-authored-by: Gabriel Radureau <[email protected]>
Co-committed-by: Gabriel Radureau <[email protected]>
- qa-strategy › Independent verification: with Mistral (vibe -p,
mistral-medium-3.5) and Ornith 35B admitted to verifier duty by verdict
parity (erp#63 evidence, blind-judged), the independent verifier SHOULD be
a different model family than the builder; journal records which family
verified what.
- STATUS: #63✅ (PR erp#69, harness home erp:fleet/harness/), #56✅
(PR erp#68, authored by the Mistral builder bench), #39 built on local
branch (push+PR = operator step), PR-log rows, P3 flipped to in-progress.
Paired with erp#69 (Closes erp#63).
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01VRShc4QhLLU73FLHx9vskh
Ajouté par bda53f29 pour « monter les credentials dans le repo-server »,
mais ces values sont passées au chart argocd-image-updater (HelmChart
kube-system) qui n'a pas de clé repoServer : no-op intégral. Le vrai
repo-server ArgoCD est déployé par l'addon k3s et n'a pas besoin de ce
montage — les credentials repo passent par les secrets étiquetés
argocd.argoproj.io/secret-type.
Co-Authored-By: Claude Fable 5 <[email protected]>
Ces champs (commit 7aa789d1) n'existent pas dans le schéma Application
d'ArgoCD : l'API server les élague à l'apply, d'où un diff permanent →
factory OutOfSync en boucle (296 tentatives selfHeal) sur kadans,
telegram-gateway et dance-lessons-coach. L'authentification aux repos
privés passe par un secret repo-creds (label
argocd.argoproj.io/secret-type: repo-creds, url préfixe
https://gitea.arcodange.lab/arcodange) — corrigé côté cluster sur le
secret gitea-credentials existant.
Co-Authored-By: Claude Fable 5 <[email protected]>
Operator direction 2026-07-15: the orchestration layer itself (builder
sessions, cold verifiers) must run on Mistral or hermes+Ornith/MLX too.
The protocol already carries everything in files+issues; new model-fleet
section defines the evidence-gated ladder — verifier roles migrate
first (cross-family refutation is stronger verification), scoped
builders benched on unchanged acceptance gates, Claude default until
the bench says otherwise. D2 row records the direction; spike = erp#63.
Co-Authored-By: Claude Fable 5 <[email protected]>
The resume-protocol fresh-reader test (context-free subagent) passed
on substance (picked erp#38, correct first command, skipped the
human-gated erp#46) and surfaced two doc gaps: milestone due dates
were only on the forge (rule says order by due date), and nothing
arbitrated one-session-one-lane vs orchestrated fan-out. Both fixed;
#54 map entry now mentions the ADC register.
Co-Authored-By: Claude Fable 5 <[email protected]>
Operator ask 2026-07-12: an ADR-equivalent for accounting so method
choices are consistent AND justifiable. Accounting scatters this across
permanence des méthodes (PCG 121-5), the annexe, the organisation doc
(PCG 911-3) and audit position memos; the ADC unifies them as one
lightweight versioned record: MADR-lite + base légale/effective-dates/
annexe-impact fields, immutable once Accepted (supersede = the
permanence principle made structural), fiscal.yaml rules cite their
ADC (écriture → règle → ADC → base légale in four hops), annexe
generated from the register, acceptance human-only. Seeds adc-001..007
from decisions already made this exercice; two new obligation-table
rows (121-5, 911-3); expert-comptable agenda updated.
Co-Authored-By: Claude Fable 5 <[email protected]>
- STATUS resume protocol: milestones ordered by due date, skip
human-gated tops, named entry points (erp#38 / #51 / write-skill
quartet); every issue now carries an Execution footer.
- Backlog map: +erp#59 (T14 split from #48), +erp#60 (T11 loop split
from #54), retitles, post-replay markers.
- D9 meeting capture parked (nice-to-have; calls are iPhone-first).
- prd_check.py preserved from the session scratchpad into scripts/
(the closure protocol references the pattern — now it's runnable).
Co-Authored-By: Claude Fable 5 <[email protected]>
Diarization and Google Calendar sync are both on Hyprnote's free plan,
which satisfies the two operator criteria at once; Meetily (MIT,
diarization in the community core) stays as OSS fallback with sb.py
ICS-matching to compensate its missing calendar sync. Gate: quality
judged on a real bilingual call before the lane is trusted (erp#49).
Co-Authored-By: Claude Fable 5 <[email protected]>
Operator has no Granola account (proprietary, paid, cloud
transcription — misfit with the vault doctrine). The delivery-agents
backlog line now specifies the local transcription lane: Whisper-class
model on the M4 + Ornith summary, as an sb.py job.
Co-Authored-By: Claude Fable 5 <[email protected]>
Operator insight 2026-07-11: the second brain's client dossiers
(01-Projects/Clients/<client>/) are where the back-office fleet meets
the sales chain and the future delivery agents working on client
projects. Knowledge-layer bullet: agent-owned deposit files (never
edits of human notes), each family feeds and reads the dossier
(billing state + contract facts in; dunning tone, contract scope,
new-business sightings out — the KM 4% settlement clause makes
delivery observations a billing input); vault routing doctrine
(local-first) for confidential client content. Backlog updated with
the 2026-07-11 CRM audit facts (propal/contrat/projet modules empty,
KM retainer terms only in WIP JSON — deferred schedule = T06's source
of truth) and the third family (delivery agents; Granola→vault ingest
as an sb.py job; REX → Mode B evidence → CMS).
Co-Authored-By: Claude Fable 5 <[email protected]>
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]>
Operator requirement: the resume protocol's twin. A milestone closes
only through an 8-step sweep that OPENS with a QA gate (acceptance
criteria re-verified with evidence, test suites green — golden-set
bars, injection fixtures, linter suites incl. seeded-wrong provenance
FAILs, idempotency replay; catalog status flips backed by proving-
protocol evidence; can't pass QA -> shed scope, don't close) and ENDS
with doc-surface QA (link/anchor check, deprecation grep of the claims
the milestone retired, fresh-reader smoke test by a context-free
subagent). In between: STATUS flip, roadmap re-baseline (diamonds never
move), no-tombstone truth-pass of leaves, AGENTS.md/SKILL.md/guidebook
sweep, outward loop (ADRs, memories, second-brain REX). Reader's half:
trust order live system > code > STATUS > leaves > memories; stale
Last Updated = suspect. Summarized as a house rule in factory
AGENTS.md, canonical checklist in the PRD STATUS.
Co-Authored-By: Claude Fable 5 <[email protected]>
Agents discovering the lab through AGENTS.md now find the decomposed
AI back-office backlog (STATUS map, erp milestones, gateway issues,
factory#22 ADR tracking) and the resume protocol.
Co-Authored-By: Claude Fable 5 <[email protected]>
Operator request 2026-07-11: decompose the PRD into less-high-level,
unambiguous work items. 23 self-contained issues filed (context,
deliverables, acceptance criteria, dependencies, PRD links): erp#38-57
across 6 dated milestones (P1 flagship, P2 e-invoicing hard 09-01,
P3 standing fleet, ledger compliance, P4 money loops, P5 fiscal),
telegram-gateway#1-2 (queue D1 + digest/cards), factory#22 (ADR
tracking for D1/D2/D4/D6/D7). STATUS phase tracker now points each
phase at its milestone; resume protocol for future sessions: pick the
top unblocked issue of the earliest open milestone.
Co-Authored-By: Claude Fable 5 <[email protected]>
Operator requirement (2026-07-11): mechanical measures against
hallucinated values in agent-performed edits. Four deterministic
layers in agent-architecture: (1) no free-text critical fields —
code copies values from validated extraction/bank-feed payloads,
the model never re-types them; (2) provenance anchors per critical
field {source_sha256, raw_excerpt} re-verified by a deterministic
checker against re-extracted source text (locale-normalized);
(3) cross-system corroboration against FRESH bank pulls + arithmetic
/checksum invariants; (4) read-back closes the loop. A failed check
refuses, never repairs. Woven into hub goal, C1 strategy, and QA
write-path (seeded-wrong-value fixtures must FAIL). Proven live:
the books-regularization pack shipped with a standalone
verify-provenance checker — 36 field-level checks green against
source PDFs + fresh Qonto/Wise feeds before the human gate.
Co-Authored-By: Claude Fable 5 <[email protected]>
New agent-catalog.md leaf answering three operator directions:
(1) the document surface agents read is now specified — AGENTS.md
orientation maps, SKILL.md with trigger-carrying descriptions
(Use-when/SKIP-for), atom.yaml registry contracts, thin prompt.md
files (no business rules in prompts — rules live in profile files
and validators), schema'd fiscal.yaml/calendar.yaml with
effective_from dates, [AGENT]/[HUMAN] runbooks, env-var-indirected
.mcp.json; same-change freshness rule extended to the fleet.
(2) hermes's telegram-gateway confirmed as THE human channel when
available (cluster-served cards, email fallback; D1 operator-endorsed).
(3) the categorization to prove: seven agent classes (incl. the
LLM-free deterministic controller) and a T01-T17 assignment matrix
with per-row proof gates and statuses (proven / to-prove / not-built),
re-scored monthly from run journals (fallback rate >20% = wrong cell).
New D8 (fleet code home, leaning erp fleet/).
Co-Authored-By: Claude Fable 5 <[email protected]>
New roadmap.md: mermaid Gantt (validated) putting the six phases on
calendar time from baseline 2026-07-11 — P2 e-invoicing opens the plan
(ADR D4 target 08-14, two-week fallback before the hard 09-01), P1
flagship in parallel (golden set first, A2 earned ~10-09), ledger-
compliance verifications early September (FY-2026 FEC depends on the
accounting-module answer), P3 standing fleet through autumn (sentinel
24/7 ~11-13), P4 money-loop exit over December, P5 riding the fiscal
calendar (acompte 12-15, CA3 switch 01-01, Q1 filing April, CA12 early
May, AG 06-30), P6 e-reporting proven months before 2027-09-01.
Immovable-milestone table, dependency notes, re-baselining rule
(engineering bars slide, diamonds don't — slips shed scope instead).
Wired: hub pointer + leaves row, poc-plan/STATUS backlinks.
Co-Authored-By: Claude Fable 5 <[email protected]>
New compliance.md leaf: French bookkeeping obligations mapped to fleet
mechanisms — inaltérabilité (L.123-22) via an append-only production
ledger grammar (create/validate/pay/avoir, never mutate a validated
document) enforced by a promote-plan compliance linter; FEC (L.47 A
LPF) with quarterly export + Test Compta Demat validation (accounting-
module binding flagged as unverified gap); piste d'audit fiable (289
VII CGI) framed as a by-product of journals + tx-id reco + monthly
packs; retention, numbering, copie fiable; loi anti-fraude scoped out
(B2B-only) with BlockedLog as sandbox-first belt-and-braces.
New Environments section in agent-architecture: prod = the ledger
(grammar-bound), sandbox = disposable iso-prod rehearsal (exempt, never
wired to production third parties); side_effect_class -> environment/
credential mapping; POCs write on sandbox only; evals target fresh
checkpoints; irreversible-by-design features trial on checkpoints.
Woven through hub (goal, requirement, success criteria, leaves table),
T03/T05/T15 guardrails, QA (linter suite, pure-append snapshots, FEC
cadence, PAF evidence framing), C2, POC-1 exit criteria.
Co-Authored-By: Claude Fable 5 <[email protected]>
Operator direction: Qonto is the capital-deposit bank, a DGFiP-registered
PA with e-invoicing included in every plan, and the fleet's richest
existing API integration. POC-6 rewritten from shortlist to validation
spike (reception + business-API pull + GED archival + 2027 e-reporting
path for the KM export invoices); C12 residual now names the vendor-
concentration trade-off; terminology refreshed PDP -> PA (renamed by the
administration in July 2025); 2027-09 milestone clarified (e-reporting
for export invoices; emission only if a French B2B client arrives).
Co-Authored-By: Claude Fable 5 <[email protected]>
The PARA Obsidian vault (arcodange/SecondBrain — git-synced, sb.py
digest/inbox/gitea-ingest jobs on the hermes cron ticker, local Ornith
model, mcp-obsidian access) enters the PRD as a first-class component:
new T17 knowledge capture & retrieval fiche, knowledge-layer section in
the architecture (ERP = book of record, vault = context + institutional
memory, append-only idempotent deposits, trusted-but-stale retrieval),
hermes/Ornith recognized as the resident M4 runtime (D2 leaning, new D7
cluster<->vault access decision), foundation ledger row, diagram +
goals updated (mermaid revalidated, 231 links/anchors re-checked green).
Co-Authored-By: Claude Fable 5 <[email protected]>
New PRD tree vibe/PRD/ai-back-office/ (hub + 6 leaves + STATUS): task
inventory T01-T16 with mode operatoire, atom/contract architecture on
the ADR-0003 write gate, four-tier model fleet (Claude/Mistral/M4/Pi),
12 challenges with mitigations, POC plan with exit criteria, QA
strategy with autonomy promotion gates. Index row + bidirectional
backlinks (erp guidebook, safe-prod PRD).
Co-Authored-By: Claude Fable 5 <[email protected]>
Records how erp-sandbox's DATA is seeded, reset, and kept structurally
incapable of harming prod — the application-data-layer complement to ADR-0001
(which rejected an in-cluster sandbox for INFRA rehearsal) and the lifecycle for
the erp-sandbox instance ADR-0002 stood up.
Decision: (1) iso-prod golden via read-only pg_dump of prod erp, app-scoped to
llx_*; (2) reset = DROP OWNED BY erp_sandbox_role CASCADE + pg_restore
--no-owner --role=erp_sandbox_role into the EXISTING db (no DROP/CREATE DATABASE,
no CREATEDB, no superuser; provisioner-owned infra objects like the pgbouncer
user_lookup function are left untouched); (3) prod-write isolation as a
structural invariant (superuser only in human-gated postgres.yaml CI; DROP
DATABASE gated by ownership — erp_sandbox_role owns only erp-sandbox, never prod
erp/erp_role; sandbox-scoped Dolibarr key; membership-only runtime creds;
host-guard; resettability); plus a human-gated promote via the read-only
dolibarr-data-snapshot diff under a separate prod-write credential.
The reset mechanism + the integrity invariant were validated against the live
erp-sandbox: DROP OWNED BY erp_sandbox_role + app-scoped pg_restore round-trips
to the golden checkpoint using only erp_sandbox_role membership (superuser=false,
createdb=false, not a member of erp_role), proving prod is structurally
unreachable from the sandbox credential.
Drafted via a clean-context agent; mechanism refined from a live prototype.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
ADR-0002 Phase D, final step. Adds `envs: { sandbox: {} }` to the erp entry
in argocd/values.yaml, so the Phase B per-env loop in templates/apps.yaml
renders an extra Application "erp-sandbox":
- source: same erp repo + chart, overlaid with values.yaml + values-sandbox.yaml
- destination namespace: erp-sandbox (CreateNamespace=true)
- syncPolicy: automated prune + selfHeal (default)
GitOps activation: on merge to main, the factory app-of-apps re-renders and
ArgoCD creates the erp-sandbox Application, which deploys the Dolibarr chart
into the erp-sandbox namespace. The pod's VSO reads the Vault paths created in
D2/D3 (auth/kubernetes/role/erp-sandbox, postgres/creds/erp-sandbox,
kvv2/erp-sandbox/config) and connects to the erp-sandbox DB created in D1.
Render verified: the only diff vs main is the added erp-sandbox Application;
prod erp + all other apps render byte-identical.
No DNS/TLS change needed (Phase E): *.arcodange.lab is a wildcard in Pi-hole
(CoreDNS forwards to it) and cert-manager holds a *.arcodange.lab wildcard set
as Traefik's default TLS — so erp-sandbox.arcodange.lab resolves + gets HTTPS
automatically once the ingress is up.
Completes Phase D. D1=factory#17, D2=tools#3, D3=erp#12 (all merged).
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Activates the sandbox environment for the ERP on the Postgres side
(ADR-0002 Phase D). `erp` gains `envs = ["prod", "sandbox"]`, so the
elision flatten now materialises a second instance `erp-sandbox`:
- database `erp-sandbox`
- owner role `erp_sandbox_role` (snake-case per the convention)
- pgbouncer user_lookup function + grants for the new DB
The prod `erp` instance is unchanged (db `erp`, role `erp_role`) — the
apply is purely additive (~6 resources for erp-sandbox, 0 changed,
0 destroyed on everything else). Verified the flatten output with a
standalone tofu apply before pushing.
This is D1 of the Phase D activation. D2 (tools Vault policies),
D3 (erp iac creds + KV), D4 (ArgoCD Application) follow in order.
Refs ADR-0002 (factory#15), Phase B (factory#16).
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
ADR-0002 Phase B. Makes postgres/iac, argocd, and the conventions docs
multi-environment-capable WITHOUT activating any sandbox yet — every app
stays prod-only, so this change is behaviour-neutral:
- postgres/iac `tofu plan` is a no-op (proven: the elision flatten keys
are bare app names, db=<app>, role=<app>_role — identical addresses)
- the argocd apps.yaml render is byte-identical (181→181 lines, diff
empty) since no app declares `envs`
postgres/iac:
- variables.tf: `applications` becomes set(object({name, envs=optional(["prod"])}))
- main.tf: a `local.app_instances` flatten of applications × envs keyed by the
elided instance id (env=prod → "<app>"); per-app resources iterate it and
reference each.key / each.value.{database,role}. For prod-only apps every
resource address + attribute is unchanged. (main.tf also got a full
`tofu fmt` pass — the pgbouncer function block reindents 4→2 spaces, which
is cosmetic; the correctness gate is the CI tofu plan, not the text diff.)
- terraform.tfvars: string entries → { name = "..." } objects.
argocd/templates/apps.yaml:
- after the prod Application, a `range $app_attr.envs` loop renders one extra
Application per non-prod env: name/namespace `<app>-<env>`, shared repoURL,
helm.valueFiles [values.yaml, values-<env>.yaml], per-env syncPolicy override.
Renders nothing while no app sets `envs` → prod render unchanged.
docs:
- doc/runbooks/new-web-app/conventions.md (FR, authoritative): new section
"Plusieurs environnements pour une même app" — elision rule, suffix rule,
snake-case owner-role exception, erp/erp-sandbox table, ADR-0002 link.
- vibe/guidebooks/lab-ecosystem/naming-conventions.md (EN mirror): the env
coordinate section + a "Two sandbox models" section reconciling the
separate-cluster (ADR-0001, names repeat) vs in-cluster sibling (ADR-0002,
<env> suffix) strategies; Last Updated bumped; ADR-0002 cross-links.
Activation (erp gets envs=["prod","sandbox"] in postgres tfvars + argocd
values + erp/iac) is Phase D, gated by its own plan review.
Refs ADR-0002 (factory#15). Phase A = tools#2 (merged). Phase C = erp#11 (merged).
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Replaces the placeholder References line with the PR URL so the
ADR↔PR crosslink is bidirectional per the AGENTS.md rule.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Records the decision to extend the <app> join key with a second
coordinate <env>, governed by an elision rule (env=prod elides → every
existing app's derived names are byte-identical and its tofu plan is a
no-op; non-prod envs take the <app>-<env> suffix, with the Postgres
owner role staying snake-case <app>_<env>_role).
Motivated by the ERP's incoming write-capable AI-agent skill: it needs
an in-cluster sandbox instance (erp-sandbox) with a prod-like Dolibarr
API + isolated database to rehearse writes before a human promotes them
to prod. The ADR reconciles this against ADR-0001 honestly — ADR-0001
rejected an in-cluster sandbox for INFRA-change rehearsal (shared
fleet-wide control planes); ADR-0002 operates one layer up where the
agent's only reach is the app's HTTP API against an isolated DB, so the
fleet blast radius is not in scope. The two are complementary; ADR-0002
does not supersede ADR-0001.
Also:
- vibe/ADR/README.md: index row for 0002 + Last Updated 2026-06-25
- PRD safe-prod-like-environment README: bidirectional back-link to
ADR-0002 on the Adjacent line + Last Updated 2026-06-25
Authored via the ADR Scribe persona, validated via the Continuity Warden
checklist (no-tombstone, breadcrumb, MADR-lite sections, dead-link scan,
bidirectional links).
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
The one-time import block from the previous change reconciled
cloudflare_r2_bucket.arcodange_tf into state (run #29: "Import complete",
"Apply complete! Resources: 1 imported"). It is now a no-op, so remove it.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Run #28 applied cleanly except cloudflare_r2_bucket.arcodange_tf: the bucket
exists in the EU jurisdiction, but its prior state entry lacked the jurisdiction,
so cloudflare provider >=5.20 read it as not-found, removed it from state, and
then failed to recreate it ("already exists"). Add a config-driven import block
with the jurisdiction-qualified id (<account_id>/<bucket_name>/<jurisdiction>) so
the next apply adopts the real bucket. No-op once reconciled; removable after.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
With the runner CA fix (#11) the iac workflow now runs far enough to apply,
which exposed two provider problems:
cloudflare drift — `cloudflare/cloudflare` floated on `~> 5` with no committed
lock file, so CI pulled v5.21.1 where `cloudflare_account_token.policies[].resources`
is a JSON string, not a map ("Incorrect attribute value type"). Fix:
- pin to `~> 5.21` and commit a multi-platform `.terraform.lock.hcl`
(linux_arm64 for the runner + darwin_arm64 for local);
- `jsonencode(...)` the module's policy resources;
- bind the cloudflare_token module to `cloudflare/cloudflare` explicitly (it was
defaulting to `hashicorp/cloudflare`, pulling a redundant provider);
- stop `.gitignore` from hiding the lock file (the old `.terraform.*` rule did).
gitea provider TLS — it runs inside the dflook/terraform-apply container, which
doesn't trust the homelab CA (only the ubuntu-latest-ca runner does), so it
failed `x509: certificate signed by unknown authority` reaching
gitea.arcodange.lab. Fix: feed it the homelab CA via the provider's `cacert_file`
(TF_VAR_gitea_cacert_file -> the homelab.pem the workflow already materializes).
Validated locally with `tofu validate` + provider-schema inspection (no prod
calls). Complements #11. Out of scope (need a live run / operator): the OVH
consumer-key scope, and the R2 bucket "not found" on refresh (a state reconcile).
Co-Authored-By: Claude Opus 4.8 <[email protected]>
After the move to the self-signed internal DNS (gitea.arcodange.lab /
vault.arcodange.lab), the default `ubuntu-latest` runner image does not
trust the homelab CA, so the `uses:` clone of the vault-action over HTTPS
fails TLS verification. webapp's workflows already moved to the
`ubuntu-latest-ca` runner (whose image ships the homelab CA); apply the
same to the factory `iac` and `postgres` tofu workflows.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Per-session Claude Code checkouts live under .claude/worktrees/<slug>/
on the trunk; keep them out of git so the main checkout stays clean.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
The two factory-provisioning sub-hubs were the only guidebook index pages without
the "alter a documented component -> update its page in the same PR" reminder that
every sibling hub carries. Add a scoped maintenance rule to each, pointing back to
the factory-provisioning maintenance rule and the guidebooks' Rules to contribute,
so no folder hub silently drifts.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Two agent-oriented runbooks under vibe/runbooks/ with [AGENT]/[HUMAN] step
markers, grounded in real diffs:
- new-tool.md : add a platform component to the tools repo so ArgoCD deploys it
into the tools namespace (wrapper Chart.yaml + the tool library + a row in
chart/values.yaml; optional iac/ for secrets). Mirrors the prometheus/crowdsec
additions.
- new-app.md : stand up a brand-new application across THREE repos (app +
factory + tools) with the strict ordering dependency and the TERRAFORM_SSH_KEY
pitfall. Phase-by-phase mapped to the dance-lessons-coach onboarding PRs
(#89/#97/#98/#99/#100), factory #1/#2, tools #1; the FR doc/runbooks/new-web-app
is linked as the detailed companion.
2 mermaid diagrams MCP-validated; zero dead links across the vibe tree.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Tree-docs guidebook under vibe/guidebooks/applications/ documenting the common
app pattern and two contrasting archetypes, drilling into lab-ecosystem/01-factory
(bidirectional):
- README.md : the shared app pattern (repo = Dockerfile + chart + optional iac +
CI; ArgoCD app-of-apps; the <app> join key; .fr vs .lab ingress conventions) +
a two-archetype comparison.
- webapp.md : canonical Go + Postgres exemplar (chart, VaultAuth/Static/Dynamic
CRDs, inline iac vs the shared app_roles module, CI); notes the current nuance
that the live pod still uses the static pgbouncer_auth DATABASE_URL.
- url-shortener.md : Rust + SQLite-on-Longhorn-RWO counterpart (single replica,
no iac/no Vault, CI mirrors the upstream image); the power-cut recovery story.
erp is referenced in prose only (its own guidebook lands next). Sibling-repo code
via full gitea URLs; 2 mermaid diagrams MCP-validated; zero dead links.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Deep, code-grounded tree-docs guidebook under vibe/guidebooks/factory-provisioning/,
explored from the actual playbooks/roles and tofu code:
- Hub: the two provisioning engines (operator-run Ansible vs CI-applied OpenTofu),
a green-field bring-up flow, master index, maintenance rule.
- ansible/ sub-tree: ordered pages 01-system .. 06-recover, an inventory & variables
concept page, and a Tier-1/Tier-2 roles reference (hashicorp_vault, step_ca,
crowdsec, pihole, deploy_docker_compose + the gitea_* family and helpers).
- opentofu/ sub-tree: factory-iac (Cloudflare/OVH/GCP/Gitea/Vault edge +
cloudflare_token module), postgres-iac (per-app DB/role/pgbouncer lookup),
ci-apply-flow (Gitea OIDC-JWT -> Vault -> auto-approve apply).
Cross-linked bidirectionally with the lab-ecosystem guidebook and the safe-env
ADR/PRD (the sandbox rehearses exactly these engines). 14 mermaid diagrams
MCP-validated; zero dead links. Authored by the Lab Cartographer cohort.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Add a root AGENTS.md (ecosystem map of factory/tools/cms + agent operating
rules + the persona cohort & workflow) and a new vibe/ knowledge base for LLM
agents, modeled on tree-docs conventions and the factory house style.
vibe/ folders (each with a README hub + contribution rules):
- ADR/ optimized MADR-lite; canonical home going forward (doc/adr stays historical)
- PRD/ one subfolder per PRD, mandatory STATUS.md, QA strategy for big ones
- investigations/ single INV-NNN-slug.md, or stub + folder w/ notebooks
- guidebooks/ tree-docs maps; lab-ecosystem guidebook of factory+tools+cms
- runbooks/ [AGENT]/[HUMAN] step procedures (EN; doc/runbooks stays FR)
- shareouts/ dated FR handouts (decks/mp4)
Seed content (first ADR + PRD): a safe, production-like environment to rehearse
risky changes and recovery without touching real prod — local-only sandbox
(k3d + arm64 VMs) with a hard prod/sandbox isolation boundary. Includes
INV-001 (prod blast-radius couplings), the ecosystem guidebook, and a FR shareout.
Conventions enforced: no-tombstone rule, breadcrumb spine, bidirectional
cross-links, theme:base mermaid (MCP-validated) + ordered-list-after-diagram.
Built with a Workflow + persona cohort; 24 files, zero dead links.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Document, as a tree-docs tree, the end-to-end procedure to stand up a new
web application on the Arcodange platform — a mechanic spread across the
factory, tools and app repos with non-trivial ordering dependencies.
Covers: Gitea repo creation (org-secret inheritance), Postgres DB + owner
role (factory/postgres/iac), platform Vault declaration (gitea_cicd_<app>
+ policies, tools/hashicorp-vault/iac), the app Helm chart (VSO dynamic
secrets via pgbouncer), the app Terraform (app_roles module), the CI
workflows (tofu apply + image build, incl. the copy-pasted role pitfall),
and ArgoCD registration (factory/argocd/values.yaml). Adds a naming-
conventions concept page and an ordered checklist.
Wires the legacy doc/adr "setup hello world web app" item and the factory
README to the runbook. New docs live under doc/ (singular) per the PR #8
convention.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
The 20260509 ADR landed in docs/adr/ (plural) by mistake. Convention
is doc/adr/ (alongside the existing 00_*, 01_*, … docs and the
network-architecture/cicd-architecture ADRs that pre-existed there).
Note : 20260407-*.md files in the typo'd docs/adr/ are still untracked
(never committed) — separate cleanup task.