fix(budget): une table sans aucune ligne datée n'est jamais « 0 € vérifié »

Passe /autofeedback (angle checkpoint) : si NocoDB renvoie des lignes
sans timestamp, CreatedAt ni created_at, checkBudget rendait
verified=true à 0 €, soit le fail-open silencieux d'avant sous une autre
forme. Désormais verified=false et avertissement au journal. 17/17.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0174RrDEQFQTtySXkTcsUKuv
This commit is contained in:
Jules Neny
2026-09-29 00:27:01 +02:00
co-authored by Claude Opus 5.5
parent 74337c0bcb
commit a2b89489a6
2 changed files with 19 additions and 0 deletions
+10
View File
@@ -128,6 +128,16 @@ try {
check('journal : lignes sans date signalées', logs.some(([, t]) => t.includes('1 sans date ignorées'))) check('journal : lignes sans date signalées', logs.some(([, t]) => t.includes('1 sans date ignorées')))
} }
// 3bis. Aucune ligne datée : jamais « 0 € vérifié »
{
const rows = [{ Id: 1, cout_eur: 30 }, { Id: 2, cout_eur: 1 }]
const { fetchJson } = fauxNocodb({ schema: ['Id', 'cout_eur'], rows })
logs.length = 0
const s = await checkBudget({ ...base, fetchJson })
check('aucune date : verified=false, pas 0 € vérifié', s.verified === false && s.lignesSansDate === 2)
check('aucune date : avertissement « NON vérifié »', logs.some(([niv, t]) => niv === 'warn' && t.includes('NON vérifié')))
}
// 4. Pagination : 2 500 lignes du mois à 0,01 € → 25 € → bloqué // 4. Pagination : 2 500 lignes du mois à 0,01 € → 25 € → bloqué
{ {
const rows = Array.from({ length: 2500 }, (_, i) => ({ Id: i + 1, cout_eur: 0.01, CreatedAt: '2026-09-20T00:00:00Z' })) const rows = Array.from({ length: 2500 }, (_, i) => ({ Id: i + 1, cout_eur: 0.01, CreatedAt: '2026-09-20T00:00:00Z' }))
+9
View File
@@ -113,6 +113,15 @@ export async function checkBudget(config: {
return { cumulEur: 0, blocked: false, warning: false, verified: false, lignesLues, lignesMois, lignesSansDate } return { cumulEur: 0, blocked: false, warning: false, verified: false, lignesLues, lignesMois, lignesSansDate }
} }
// Des lignes, mais aucune datée : on ne sait pas ce qui relève du mois.
// Rendre « 0 € vérifié » reproduirait le fail-open silencieux d'avant AF5.
if (lignesLues > 0 && lignesSansDate === lignesLues) {
console.warn(
`[circuitBreaker] budget NON vérifié : ${lignesLues} lignes stats_usage sans timestamp, CreatedAt ni created_at`,
)
return { cumulEur: 0, blocked: false, warning: false, verified: false, lignesLues, lignesMois, lignesSansDate }
}
const status: BudgetStatus = { const status: BudgetStatus = {
cumulEur, cumulEur,
blocked: cumulEur >= BUDGET_MAX_EUR, blocked: cumulEur >= BUDGET_MAX_EUR,