concierge: lire airtria_ro depuis l'env/.env + alerte d'echec d'auth

Le mot de passe airtria_ro est desormais lu depuis AIRTRIA_RO_PASSWORD
(variable d'environnement, sinon .env monte dans le conteneur), plus depuis
.pg-airtria : EVCS renouvelle ce mot de passe, un fichier fige faisait echouer
l'auth EN SILENCE. readPgPassword LEVE (rattrapable) au lieu de process.exit ;
mot de passe resolu paresseusement dans makeClient.

Nouveau module concierge-health.js (meme patron que le moniteur LLM) : battement
de coeur horodate a CHAQUE cycle detect, statut live dans HA, et alerte WhatsApp
UNIQUEMENT sur transition ok<->erreur (dedup, pas de rappel periodique). Chemin
HA independant de PostgreSQL : l'alerte auth KO part meme quand c'est l'auth qui
tombe. Best-effort : si les helpers HA manquent, le concierge n'echoue pas.

- concierge-health.test.js : machine a etats (dedup + transitions) verifiee, sans reseau
- concierge-health-ha-package.yaml.draft : helpers HA (a integrer cote proprietaire)
- SPEC : source du secret corrigee + section supervision

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RZsbhyGmRwZogdoBwT6C6y
This commit is contained in:
2026-09-16 03:26:50 +02:00
parent d0805331ad
commit 6da250914d
6 changed files with 276 additions and 16 deletions

View File

@@ -17,6 +17,7 @@ const fs = require('fs');
const path = require('path');
const { makeClient } = require('./concierge-detection.js');
const { publishLabel } = require('./concierge-write.js');
const health = require('./concierge-health.js');
const STATE = path.join(__dirname, 'concierge-state.json');
const PENDING = path.join(__dirname, 'concierge-pending.json');
@@ -91,7 +92,8 @@ async function loadWindow() {
const hhmm = (epoch) => new Date(epoch * 1000)
.toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit', timeZone: 'Europe/Paris' });
async function detect() {
// Détection pure : PEUT LEVER (erreur PG/auth via loadWindow). Renvoie la question ou ''.
async function runDetection() {
const { plugins, selChanges } = await loadWindow();
if (plugins.length === 0) return '';
const latest = plugins[plugins.length - 1];
@@ -112,6 +114,19 @@ async function detect() {
`(VW / JEEP / invité) [${ref}]`;
}
// Cycle detect : publie la santé À CHAQUE passage (battement de cœur HA) et n'alerte que
// sur transition. Une panne d'auth PostgreSQL n'est donc PLUS silencieuse — elle devient un
// statut HA + un WhatsApp unique. Sur reprise, le « de nouveau opérationnel » précède
// l'éventuelle question véhicule (le branchement en attente n'est pas perdu).
async function detect() {
let question = '', ok = true, detail = null;
try { question = await runDetection(); }
catch (e) { ok = false; detail = e.message; }
const transition = await health.publish({ ok, detail });
if (ok) return [transition, question].filter(Boolean).join('\n\n');
return transition; // échec : WhatsApp seulement à la bascule vers panne
}
// Labels en MAJUSCULES sans accent (convention colonne : VW/JEEP/MEGANE). Le propriétaire
// peut nommer l'invité (MEGANE) ; sinon INVITE. ZERO accepté (moto, si option ajoutée).
function parseLabel(text, options) {