e-naxosMAF COURSE / COURS
English

MICROSOFT AGENT FRAMEWORK · 0.16.0-rc.1

Journal métier et preuve des effets

Distinguer intention, accusé et effet commis ; rouvrir la base pour vérifier la preuve locale.

Objectifs

Partir de l’effet à prouver

« La demande a été enregistrée » est une affirmation à vérifier. Une trace d’appel et un accusé de lecture ne prouvent pas une écriture. Le journal métier conserve une chronologie minimale d’événements hôte ; la table RecordedDecisions conserve l’action locale ReviewRequested. La preuve consiste à relier l’événement de commit à cette ligne réellement relue.

Chemin de la preuve locale
  1. GUID hôte → RequestReceived → workflow accepté
  2. Intention → transaction SQLite
  3. RecordedDecisions + ActionCommitted → même commit
  4. Nouveau processus → Read → HasAction

Choisir les événements et leur sens

Vocabulaire fermé du programme
MécanismePortée
RequestReceived / IntentionDemande reçue / action envisagée ; aucune écriture prouvée
AcknowledgedOutil de lecture appelé ; aucun effet métier
ActionCommitted + ActionIdCréés par CommitReview avec la ligne métier
ExecutionSucceeded / Failed / CancelledFin du scénario ; un échec ultérieur ne défait pas un commit antérieur

JournalEntry ne reçoit que GUID hôte, horodatage UTC, TraceId courant, enum fermé et ActionId optionnel. Il ne contient ni utilisateur fonctionnel, ni conversation, ni arguments/résultats d’outils, ni exception libre. L’ancien script proposait davantage de données ; le programme rénové choisit cette forme minimale pour éviter de stocker les secrets dans une prétendue preuve. Il ne révèle pas le raisonnement interne du modèle.

Garantir l’atomicité dans cette base

public long CommitReview(Guid executionId)
{
    using var connection = Open();
    using var transaction = connection.BeginTransaction();
    using var command = connection.CreateCommand();
    command.Transaction = transaction;
    command.CommandText = "INSERT INTO RecordedDecisions(ExecutionId,Decision) VALUES($id,'ReviewRequested') ON CONFLICT DO NOTHING";
    command.Parameters.AddWithValue("$id", executionId.ToString("N"));
    bool created = command.ExecuteNonQuery() == 1;
    command.CommandText = "SELECT Id FROM RecordedDecisions WHERE ExecutionId=$id";
    long id = (long)command.ExecuteScalar()!;
    if (created) Insert(connection, transaction, executionId, JournalKind.ActionCommitted, id);
    transaction.Commit();
    return id;

shared/Maf.Observability/BusinessJournal.cs · L58–L71 · 9a895c94534c

Code source · Maf.Observability · Dossier

CommitReview ouvre une transaction, insère sous la clé ExecutionId unique, relit l’identifiant, crée ActionCommitted seulement si la ligne était nouvelle puis valide. Répéter le même GUID rend la même action sans deuxième événement. Append interdit ActionCommitted : une intention ou un texte généré ne peut pas fabriquer la preuve de commit par cette API.

La corrélation relie des observations ; l’atomicité lie deux écritures à la même transaction. Elles ne sont pas interchangeables. Cette transaction ne couvre aucun système distant. Pour une approbation humaine et une reprise durable, reprendre Demo320 ; le journal ne remplace ni la permission hôte, ni la liaison à la proposition approuvée.

Relire après redémarrage

public bool HasAction(Guid executionId, long actionId)
{
    using var connection = Open();
    using var command = connection.CreateCommand();
    command.CommandText = "SELECT COUNT(*) FROM RecordedDecisions WHERE ExecutionId=$id AND Id=$action";
    command.Parameters.AddWithValue("$id", executionId.ToString("N"));
    command.Parameters.AddWithValue("$action", actionId);
    return (long)command.ExecuteScalar()! == 1;

shared/Maf.Observability/BusinessJournal.cs · L103–L110 · 9a895c94534c

Code source · Maf.Observability · Dossier

if (Value(args, "--read") is string read)
{
    var entries = journal.Read(Guid.Parse(read));
    Console.WriteLine(JsonSerializer.Serialize(entries));
    return entries.Count > 0 && entries.Where(e => e.Kind == JournalKind.ActionCommitted)
        .All(e => e.ActionId.HasValue && journal.HasAction(e.ExecutionId, e.ActionId.Value)) ? 0 : 1;

shared/Maf.Observability/ObservabilityDemo.cs · L24–L29 · 9a895c94534c

Code source · Maf.Observability · Dossier

Conserver Database et ExecutionId de l’exécution, arrêter le processus puis appeler --read avec le même fichier. Read trie par Id et conserve le TraceId d’origine. HasAction vérifie l’existence dans RecordedDecisions pour ce GUID et cet ActionId. Relever le code de sortie et lire aussi les Kind : --read peut réussir pour une chronologie non vide en échec sans ActionCommitted, puisque sa vérification des commits est alors vide. Sortie 0 ne signifie donc pas « action réalisée ».

Rétention et portée de la preuve

Au démarrage d’une nouvelle exécution, la politique du code purge les métadonnées de plus de 30 jours ; la lecture seule ne lance pas cette purge. --erase efface les métadonnées d’une exécution, sans annuler l’action. La rétention des lignes métier est séparée. SQLite effectue une suppression logique ; pages libres, copies et sauvegardes peuvent subsister. Protéger fichiers et sorties par les droits du système.

Le journal est modifiable, non signé et sans garantie append-only ou anti-altération. La base pédagogique est distincte des anciennes bases EP008, qui restent conservées. Reprendre un TraceId après lecture ne ressuscite pas un span actif ni une trace distribuée continue. Aucun exactly-once global, effet distant ou conformité réglementaire n’est démontré.

Observer le succès et l’échec

Prérequis

Pour poursuivre

Précédent

Suivant

lot012-observability-1 · 2026-10-24 · 9a895c94534cd5fc6f3da883e37e5f7856e588c1