Files
rpa_vision_v3/docs/POC/SPECS_ENDPOINT_PERSIST_2026-06-01.md

8.6 KiB
Raw Blame History

SPECS — POST /api/v1/lea/competences/candidate/persist

DRAFT specs pour implémentation — pas encore appliqué — relecture Codex/Dom attendue

Date : 2026-06-01 Auteur : Claude (agent Plan) Statut : DRAFT — aucun code écrit, aucun fichier de prod modifié Cible : agent_v0/server_v1/api_stream.py

Contexte

L'audit Claude du 2026-06-01 17h00 confirme l'absence de ce endpoint. Le cycle Shadow apprentissage actuel s'arrête à /api/v1/shadow/build (renvoie le workflow_ir en mémoire, ne le persiste pas).

Ce endpoint clôt le cycle Léa-first (6 phases) : start → stop → understanding → feedback (N×) → build → **persist**

Sources :

  • docs/coordination/inbox_codex/2026-06-01_1620_claude-to-codex_ARCHI-apprentissage-Lea-first-validee-Dom.md
  • docs/coordination/inbox_codex/2026-06-01_1745_claude-to-codex_ADDENDUM-archi-Lea-lecture-semantique-agent-externe.md
  • docs/coordination/inbox_codex/2026-06-01_1800_claude-to-codex_PRECISIONS-desapprentissage-regle-or-HDS.md

Schéma YAML de référence : data/competences/candidate/key_win_r_wait_explorer_exe.yaml

Sommaire

  1. Contrat de l'endpoint
  2. Validation pré-écriture
  3. Génération YAML
  4. Journalisation
  5. Cas particuliers
  6. Sécurité
  7. Tests à prévoir
  8. Estimation effort implémentation

1. Contrat de l'endpoint

Méthode / URL : POST /api/v1/lea/competences/candidate/persist

Headers requis :

  • Authorization: Bearer <token-poste> (cohérent avec patch P0 révocation token Codex)
  • Content-Type: application/json

Payload entrée (JSON) :

{
  "name": "Saisir texte Word",
  "machine_id": "DESKTOP-58D5CAC_windows",
  "session_id": "sess_20260601T180000_abc123",
  "workflow_ir": { "steps": [...], "preconditions": [...], "success_marker": {...} },
  "parameters": [ { "name": "texte", "type": "string", "required": true } ],
  "external_decision": {
    "agent_id": "t2a_v2_codeur",
    "decision_contract": "..."
  },
  "annotations_semantiques": {
    "intent_fr": "saisir le texte transmis dans Word"
  },
  "learning_metadata": {
    "persist_id": "uuid-v4-client",
    "partial": false,
    "dropout_reason": null,
    "source_phase": "shadow_build"
  }
}

Codes HTTP :

  • 201 Created — YAML écrit, audit journalisé
  • 400 Bad Request — payload invalide (schema, slug, agent externe inconnu)
  • 401 Unauthorized — token absent / invalide / révoqué
  • 403 Forbiddenmachine_id du payload ≠ machine du token
  • 409 Conflict — slug existe déjà dans data/competences/candidate/
  • 429 Too Many Requests — rate limit dépassé
  • 500 Internal Server Error — échec atomic write ou roundtrip

Payload sortie (201) :

{
  "competence_id": "saisir_texte_word",
  "yaml_path": "data/competences/candidate/saisir_texte_word.yaml",
  "learning_state": "candidate",
  "persist_id": "uuid-v4-serveur",
  "audit_entry_id": 4271
}

2. Validation pré-écriture

Slugification (namecompetence_id) :

  • Conversion kebab-case ASCII (translittération accents)
  • Lowercase, espaces → _, chars non [a-z0-9_] retirés
  • Longueur ≤ 80 caractères, ≥ 3 caractères
  • Pattern regex final : ^[a-z][a-z0-9_]{2,79}$
  • Si collision avec un fichier existant en candidate/ : 409 sans suffixage automatique

Schema YAML cohérent — champs obligatoires : schema_version, id, name, version, learning_state, intent, parameters, preconditions, methods, success_marker, failure_message_template, promotion, generalisation, failure_log, created_at, last_updated_at, methods_execution

WorkflowIR :

  • workflow_ir.steps non vide sauf si learning_metadata.partial == true
  • Chaque step possède kind ∈ primitives connues (réutiliser registry primitives existante)

Idempotence :

  • persist_id (UUID v4 client) déduit la clé d'idempotence
  • Si persist_id déjà présent dans persist_audit.jsonl → renvoyer 200 OK avec le payload de l'entrée précédente (pas 409)

3. Génération YAML

Chemin cible : data/competences/candidate/<slug>.yaml

Atomic write :

  1. Écrire dans data/competences/candidate/.<slug>.yaml.tmp.<persist_id>
  2. os.rename() vers <slug>.yaml (atomique POSIX)
  3. Si collision détectée entre validation et rename → 409, supprimer le .tmp

Post-write roundtrip :

  • Recharger via core.competences.catalog.load_competence(slug)
  • Vérifier que competence.to_dict() est cohérent
  • Si KO → 500, supprimer le fichier, log critique

4. Journalisation

Audit principal — append-only data/competences/persist_audit.jsonl :

{
  "persist_id": "uuid-v4",
  "timestamp": "2026-06-01T18:05:32+02:00",
  "machine_id": "...",
  "session_id": "...",
  "competence_name": "Saisir texte Word",
  "competence_id": "saisir_texte_word",
  "yaml_path": "data/competences/candidate/saisir_texte_word.yaml",
  "learning_state": "candidate",
  "partial": false,
  "dropout_reason": null,
  "external_agent_id": null,
  "audit_entry_id": 4271
}

Apprentissages incomplets — si partial: true, entrée en plus dans data/competences/incomplete_learnings.jsonl avec dropout_reason obligatoire.

Append-only strict : ouvrir en mode a, jamais réécrire. Verrou fcntl.flock par fichier.

5. Cas particuliers

Cas Comportement
learning_metadata.partial == true accepté, learning_state forcé incomplete, entrée double dans incomplete_learnings.jsonl, dropout_reason obligatoire (sinon 400)
Payload demande learning_state: stable rejet silencieux — forcer candidate (jamais stable par persist direct — règle d'or HDS)
Aucun learning_state fourni défaut candidate
external_decision.agent_id inconnu de ExternalDecisionClient registry 400 avec liste agents valides
workflow_ir.steps vide et partial == false 400
YAML déjà présent en data/competences/stable/ ou supervised/ avec même slug 409 (collision cross-states)
Désapprentissage en cours sur le slug 409 detail: "désapprentissage actif"

6. Sécurité

  • Token Bearer obligatoire — réutiliser middleware d'auth du patch P0 révocation token. Pas de fallback "agent local".
  • Couplage machine_id ↔ token : token référence machine_id ; si payload.machine_id != token.machine_id403.
  • Rate limit : 10 persists/min/machine_id (in-memory token-bucket). Au-delà → 429 avec Retry-After.
  • Pas d'écriture hors data/competences/candidate/ : path traversal interdit (slug strict, jamais lu d'un champ user).
  • Logs : ne jamais journaliser le token. persist_id et machine_id only.
  • Règle d'or HDS : aucune donnée patient ne doit transiter — refuser le payload si workflow_ir ou annotations_semantiques contient pattern PII détecté. Détection MVP via regex paramétrable.

7. Tests à prévoir

Unit (tests/unit/test_competence_persist.py) :

  • test_slug_generation_normal / _with_accents / _too_short / _collision_409
  • test_yaml_schema_required_fields_present
  • test_atomic_write_then_rename
  • test_post_write_roundtrip_ok / _corrupt_yaml_500
  • test_idempotence_same_persist_id_returns_previous
  • test_partial_true_forces_incomplete_state
  • test_payload_stable_forced_to_candidate
  • test_external_agent_unknown_400
  • test_pii_detected_rejected

Integration (tests/integration/test_shadow_full_cycle.py) :

  • Cycle complet : shadow/start → POST événements → shadow/stop → shadow/build → persist
  • Vérifier YAML présent, persist_audit.jsonl enrichi
  • Cas partial : double entrée incomplete_learnings.jsonl

Sécurité (tests/security/test_persist_auth.py) :

  • test_no_token_401
  • test_token_revoked_401
  • test_machine_id_mismatch_403
  • test_rate_limit_11th_call_429
  • test_path_traversal_in_name_blocked

8. Estimation effort implémentation

Item Volume
Handler FastAPI + Pydantic models ~80-120 lignes (api_stream.py)
Helpers (slugify, atomic_write, audit_append) ~40 lignes (nouveau module core/competences/persist.py)
Tests unit + integration + sécu ~150 lignes

Effort total : faible-moyen — 0.5 à 1 jour. Pas de dépendance externe nouvelle, réutilise core.competences.catalog et le pattern atomic-write de C-γ.

Points d'attention pour la review :

  • Confirmer comportement collision (409 strict vs suffixage _v2)
  • Valider mapping workflow_ir.steps[*].kindyaml.methods[*].kind avec registry primitives à jour
  • Décider si rate limit par machine_id ou par token
  • Statuer sur format audit_entry_id (auto-incrément vs hash)

Fin DRAFT — relecture Codex/Dom attendue avant kick-off implémentation.