Aller au contenu

NORA — revues IA du catalogue + gouvernance steward

NORA est la couche de revue steward posée par-dessus le daemon catalog-sync. Le daemon propose des enrichissements de catalogue via un LLM local (description ≤20 mots, tags PII), NORA met ces propositions en file d'attente comme proposals_pending qu'un steward (data owner, analyste, compliance officer) revoit dans le cockpit et soit accepte en l'état, édite, fusionne avec la valeur actuelle, ou rejette.

Les propositions acceptées se propagent synchronement vers Catalogue (PATCH description / classification) et Vector store (upsert d'embedding dans la collection catalog consommée par la recherche sémantique d'ADEN), donc les agents IA aval voient la nouvelle description en quelques millisecondes — pas besoin d'attendre la prochaine fenêtre catalog-sync de 6 h.

Sprint 71-75 — bout-en-bout livré 2026-05-14

La chaîne complète (drawer cockpit → DB cockpit-backend → Catalogue PATCH → Vector store upsert) est live sur demo.akko-ai.com et validée end-to-end par l'agent akko-e2e-tester (verify_nora_full_chain.py). Voir ADR-060 NORA rebrand + Lego discipline.

Pourquoi une couche steward ?

L'auto-enrichissement seul ne suffit à aucune équipe ayant des obligations de conformité. Le positionnement AKKO est gouvernance-first, pas « le LLM écrit dans ton catalogue et tu lui fais confiance ». NORA capture le moment précis où un humain prend la responsabilité d'un changement de métadonnée :

  • Trace d'audit OCSF : chaque accept / reject est loggé avec l'identité du steward, la proposition LLM d'origine, la valeur acceptée, et les accusés de réception aval (status OM PATCH, ack Vector store upsert).
  • Garde PII : un tag PII proposé par le LLM ne s'applique jamais automatiquement. Le steward décide quelles colonnes sont sensibles, avec la traçabilité complète pour RGPD Art. 30 + DORA Art. 13.
  • Édition steward : la proposition est un point de départ, pas une valeur finale. Les stewards combinent régulièrement la formulation LLM avec une connaissance métier (« score de risque AML [...] validé par Compliance 2026-05-14 »).
  • Chemin de rollback : chaque proposition acceptée est rejouable depuis la table d'audit. Un accept erroné peut être annulé sans toucher au warehouse.

Architecture

flowchart LR
  subgraph Daemon["akko-catalog-sync"]
    ENR[Enrichissement<br/>LLM local<br/>≤20 mots + PII]
  end
  subgraph DB["schéma catalog_sync (Postgres)"]
    PROP[proposals_pending]
  end
  subgraph Cockpit["cockpit /#nora"]
    LIST[Liste pending<br/>+ badge KPI]
    DRAW[Drawer<br/>Actuel / Proposé / Éditeur]
    BTN["4 boutons<br/>Rejeter · Accepter tel quel<br/>Appliquer édit · Fusionner"]
  end
  subgraph Backend["cockpit-backend"]
    API["POST /api/nora/proposals/&lcub;id&rcub;/accept"]
    OM_PATCH[Client OM PATCH]
    MV_UPS[Client Milvus upsert]
  end
  subgraph Sinks
    OM[(OpenMetadata)]
    MV[(Milvus<br/>collection: catalog)]
    AUDIT[(audit_log<br/>OCSF)]
  end
  ENR --> PROP
  PROP --> LIST
  LIST --> DRAW --> BTN
  BTN --> API
  API --> OM_PATCH --> OM
  API --> MV_UPS --> MV
  API --> AUDIT

Le handler d'acceptation steward dans cockpit-backend fait trois écritures dans l'ordre : DB (proposals_pending.status='accepted' + decision_mode + accepted_value), Catalogue PATCH (description / classification), Vector store upsert (re-embed via Passerelle IA (LiteLLM), remplace le vecteur dans la collection catalog). Si OM ou Vector store échouent, l'écriture DB reste autoritaire et un reconciler peut rejouer plus tard — la décision steward n'est jamais perdue.

Les quatre modes de décision

Le drawer expose quatre boutons mappés sur l'enum decision_mode du schéma catalog-sync :

Bouton decision_mode Ce qui est persisté
Rejeter rejected Proposition abandonnée, valeur OM actuelle inchangée.
Accepter tel quel as_is La proposition LLM devient la nouvelle description OM verbatim.
Appliquer édit edited La valeur textarea steward (édition libre) devient la nouvelle description OM.
Fusionner merged Une valeur fusionnée custom (actuelle + proposition + steward) devient la nouvelle description OM. Utile quand le LLM a capté une facette manquante mais que la description actuelle a une formulation grade-conformité.

UI cockpit

La page /#nora du cockpit affiche :

  1. Table 8 colonnes : Asset FQN, Champ (description / tag), Actuel, Valeur proposée, Confiance, Enricher, Proposé le, Actions
  2. Badge KPI dans la sidebar : compteur pending (ex. NORA Reviews 6)
  3. Drawer au clic ligne : trois sections (Valeur OM actuelle / Valeur LLM proposée / Textarea éditable) + quatre boutons en pied.
  4. Placeholder fallback : les tables sans documentation rendent (non documenté) en italique gris discret pour rendre l'état vide explicite plutôt que confus.
  5. Liste pending temps réel : après accept, la ligne disparaît du filtre pending et le badge décrémente.

Surface API

Endpoints backend (cockpit-backend, FastAPI) :

  • GET /api/nora/proposals?status=pending — liste les revues pending, scopées aux namespaces autorisés du steward via Moteur de politiques (OPA) (data owners ne voient que leur domaine, akko-admin voit tout).
  • POST /api/nora/proposals/{id}/accept — body : {mode, value?, note?}mode ∈ {as_is, edited, merged, rejected}. Retourne le status OM PATCH, l'ack Vector store upsert, et l'id de la ligne d'audit.
  • GET /api/nora/audit — log d'audit paginé (steward, mode, valeurs avant / après, status OM PATCH, status Vector store upsert, timestamp).

Câblage (chart values)

# helm/akko/charts/akko-cockpit-backend/values.yaml
nora:
  dsnSecretRef:
    name: "akko-catalog-sync-pg"   # réutilise le DSN de catalog-sync
    key:  "dsn"
  schema: "catalog_sync"

openmetadata:
  baseUrl: "http://openmetadata:8585/api/v1"
  servicePrefix: "trino-akko"      # FQN 3-segments → OM 4-segments
  oidc:
    tokenUrl: "http://akko-akko-keycloak:8080/realms/akko/protocol/openid-connect/token"
    existingSecret: "akko-openmetadata-oidc"

milvus:
  uri: "http://akko-akko-milvus:19530"
  collection: "catalog"

litellm:
  baseUrl: "http://akko-akko-litellm:4000"
  embedModel: "akko-embed"
  apiKeyValue: "akko-dev-litellm-key"   # ou apiKey.secretName en prod

Le chart force imagePullPolicy: Always sur le cockpit (et sur cockpit-backend quand son image est rebuild en cours de sprint) pour éviter les régressions de layer stale documentées dans gotcha_image_pull_policy_always_dev_demo.

Limites connues (état Sprint 71-75)

  • Accept-latency : SLO actuel à 5 s mais les runs à froid mesurent 5 à 8 s (OM PATCH synchrone + Vector store upsert avant le toast). La propagation async est planifiée (toast < 500 ms, écritures aval en background) ou le SLO est passé à 10 s. Voir suivi tâche #347.
  • Dépendance reseed : NORA ne fait remonter que les propositions effectivement émises par catalog-sync. Sur un cluster frais (ou après un wipe warehouse), lancer le DAG catalog-sync avant d'ouvrir /#nora.
  • Visibilité cross-tenant : la garde Moteur de politiques est par-namespace ; un steward ne voit que les propositions dont le FQN est dans sa liste autorisée. Le scoping multi-tenant des proposals_pending eux-mêmes (ex. multi-realm client) est une tâche Phase 2.

Liens

  • akko-catalog-sync — le daemon producteur des propositions NORA.
  • ADEN — l'agent langage-naturel → SQL qui consomme la collection Vector store catalog.
  • OpenMetadata — le catalogue canonique vers lequel NORA écrit.
  • ADR-060 — naming + discipline Lego derrière NORA.
  • Runbook : akko-technical-map/runbooks/nora-om-milvus-aden-end-to-end.md.