feat: route GET /defenseurs/findings?project=X #3
Labels
No labels
source:analyste
source:defenseur
source:human
source:medic
status:approved
status:blocked
status:in-progress
status:needs-fix
status:ready
status:review
status:triage
type:bug
type:feature
type:infra
type:refactor
type:schema
type:security
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: maximus/vps-health-api#3
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Contexte
Exposer les findings detailles d'un Defenseur via HTTP authentifie. Aujourd'hui la route
GET /defenseurs(vps-health-api/index.js:210-221) sert uniquement/data/defenseurs/status.json(resume executif). On veut une seconde route exposant le detail des findings (id,description,recommendation,location,status).Issue d'origine : spike
analyse-vulnerabilite(archive 2026-04-23).Consommateurs identifies
la-compagnie-maximus) —src/app/api/admin/monitoring/route.ts:255-279consomme deja/defenseursvia HTTP+Bearer (VPS_HEALTH_TOKEN). Vercel ne peut PAS faire SSH/Tailscale au VPS, donc HTTP est la seule voie pour le drill-down findings. Aucun drill-down par projet n'existe encore cote consommateur (route 100% greenfield cote Vercel)./warmup— Tourne en local, mais HTTP > SSH (latence ~100ms vs ~1-2s)./analyse-vulnerabilite— Devient portable hors Tailscale.Etat des prerequis defenseurs (verifie 2026-05-03)
src/lookup.ts(PR #51bf47f6b)npm run findings(PR #529cc0b3f)agents-map.json:defenseurs/src/sergent.ts:588-599 writeAgentsMap(), mergee via PR #54 (9b4e432 feat(sergent): write agents-map.json snapshot for vps-health-api)ssh ubuntu@vps-b0826277.tail3c811f.ts.net 'ls -la /home/defenseur/defenseurs/agents-map.json'— confirmer presence. Si absent, attendre prochain run cron du Sergent ou trigger manuel. Merge bloque tant que prod pas prete.Decouverte critique (exploration 2026-04-26)
Le container
vps-health-apiest isole du filesystem VPS : il n'a acces qu'a/data/defenseurs/status.jsonvia bind-mount Coolify. Cela elimine :defenseursdans le container + cold-start tsx ~1-2sDecision : Option 3 — lecture fichier brut via nouveaux bind-mounts.
Implementation
Coolify : bind-mounts read-only
/home/defenseur/defenseurs/reports/->/data/defenseurs/reports/(deja en place via PR #6/reports/scans)/home/defenseur/defenseurs/agents-map.json->/data/defenseurs/agents-map.json(NOUVEAU, a verifier en debut de /fix-issue)vps-health-api/index.js — route
GET /defenseurs/findingsBearer HEALTH_TOKENque les autres routesproject=<X>(obligatoire) — ex.la-suite-booking,la-compagnie-maximuscategory=<deps|secrets|code|acces|infra>(optionnel, exact match)severity=<CRITICAL|HIGH|MEDIUM|LOW|INFO>(optionnel, threshold inclusif vers le haut)severity: retourne MEDIUM+HIGH+CRITICAL (cache LOW+INFO, bruit)?severity=LOWretourne LOW+MEDIUM+HIGH+CRITICAL — INFO toujours considere comme bruit et accessible uniquement via?severity=INFOexplicite (asymetrie volontaire avec la regle "inclusif vers le haut")DEFENSEURS_AGENTS_MAP_PATH(default/data/defenseurs/agents-map.json)project->agent. 404 si inconnu.findLatestReportForAgent(agent)(factorise depuis/reports/scans+isScanReport) -> report le plus recent par mtimecategory(exact) +severity(threshold inclusif vers le haut, avec asymetrie INFO ci-dessus){ agent, project, timestamp, findings: Finding[] }Reuse depuis PR #6
Factoriser modestement avec
/reports/scans:isScanReport()(index.js:88-96) reutilise tel quelfindLatestReportForAgent(agent)extrait du patternreaddir + filter + sort mtimedeja present danscollectScanReportsFromDir/readScanReportsForDateFormat Finding (verifie :
defenseurs/src/types.ts:3-13)Reponses
{ agent, project, timestamp, findings: Finding[] }si report present (findings peut etre vide => scan clean, pas de champstatus){ findings: [], status: "no_data" }si pas de report du tout pour l'agent (distinguer "scan clean" vs "pas de data")projectmanquantagents-map.jsonTests (bootstrap vitest)
vps-health-apin'a actuellement aucun test. Bootstrapervitest(devDep, runtime reste 0-dep) et couvrir :Authorizationproject?category=deps?severity=HIGH(threshold : retourne HIGH+CRITICAL)?severity=LOW(retourne LOW+MEDIUM+HIGH+CRITICAL, cache INFO)status{findings: [], status: "no_data"}si report absentMocks
node:fs(pattern existant cotedefenseurs/src/__tests__).Modeles vitest dispo :
la-suite-booking/vitest.config.ts,maximus-api/vitest.config.ts.Mapping projet -> agent
Decision : snapshot JSON ecrit par le Sergent (regenere a chaque run, ecriture en remplacement).
Source de verite unique cote
defenseurs.vps-health-apireste sans connaissance interne du layoutdefenseurs.Acceptation
GET /defenseurs/findings?project=la-suite-bookingretourne le reportdefenseur-bookingdetailleprojectmanquant?category=exact match?severity=Xthreshold inclusif vers le haut (HIGH retourne HIGH+CRITICAL, LOW retourne LOW+MEDIUM+HIGH+CRITICAL en cachant INFO)?severity=: retourne MEDIUM+HIGH+CRITICAL (cache LOW+INFO)status{findings: [], status: "no_data"}agents-map.jsonconfigure et verifie en prod (SSH check au debut de /fix-issue)agents-map.jsonpresent sur le VPS (verifier apres prochain run du Sergent)README.md+CLAUDE.mddocumentent la nouvelle route et les bind-mountsComplexite
Medium — code applicatif simple (~50 lignes), mais 4 sources de friction :
agents-map.jsondoit etre present sur le VPS)Decisions retenues (resolues via /analyze 2026-05-07 + 2026-05-12)
agents-map.json: a verifier ensemble au debut de/fix-issuevia SSH ; merge bloque tant que prod pas prete?severity=X-> threshold inclusif vers le haut (HIGHretourne HIGH+CRITICAL,LOWretourne LOW+MEDIUM+HIGH+CRITICAL mais cache toujours INFO)?severity=INFOexpliciteisScanReport(tel quel) + nouveau helperfindLatestReportForAgent(agent)partage avec/reports/scans; minimum, sans abstraction premature{findings: [], status: "no_data"}{agent, project, timestamp, findings: []}SANS champstatus(distingue les deux cas cote consommateur)/defenseurs/findings(plus explicite que query param)HEALTH_TOKENpartage (a reevaluer si nouveaux consommateurs externes)Notes d'implementation
fs.promisesasync pour ne pas bloquer l'event loop (~quelques KB par report, plusieurs lectures par requete).{agent}_*.jsonpeut retourner plusieurs fichiers (purgewriteReportest sur agent uniquement, voirdefenseurs/src/report.ts:10-27) — prendre le plus recent par mtime.agents-map.jsonest regenere par le Sergent a chaque run (ecriture en remplacement, pas mise a jour en place).findLatestReportForAgent, scanner REPORTS_DIR ET REPORTS_DIR/archive (cf. PR #72e75655— archive fallback) puisque le Sergent rotation post-07:30 UTC ; sinon ~22h/jour le report ne serait visible que dans archive.feat: route GET /defenseurs/findings?project=X (optionnel)to feat: route GET /defenseurs/findings?project=X