Aller au contenu

Tests d'acceptation

En bref

  • Un test d'acceptation vérifie un critère d'un besoin de Besoins par profil, avec des valeurs littérales et par les points d'entrée publics.
  • Chaque test porte l'identifiant AT-<besoin>-<n>, le même dans toutes les langues.
  • La page confronte chaque test à la suite existante et le marque couvert, partiel ou absent.
  • Les tests écrits pour combler les manques sont dans tests/acceptance/.

Les décisions qui restent à prendre sont dans Points à régler. Les termes sont définis dans le glossaire.

Comment lire les tableaux

  • « Tests existants » cite des nœuds pytest relatifs à la racine du dépôt piighost. Un nœud préfixé piighost-api: vient du dépôt piighost-api.
  • Les tests LlamaIndex sont ignorés sans l'extra llama-index. Ils passent avec uv run --with llama-index-core pytest tests/integrations/llama_index.
  • Les valeurs d'exemple des scénarios sont en français. Les tests emploient leurs équivalents anglais, par exemple « Hi Emma » pour « Bonjour Claire ».
  • Statut : couvert (le comportement est vérifié, parfois avec d'autres valeurs que celles de l'inventaire, et ces valeurs sont alors signalées), partiel (ce qui manque est indiqué), absent.
  • Les tests de la bibliothèque utilisent ExactMatchDetector ou un catalogue simulé. La qualité de détection d'un vrai modèle ou d'un vrai groupe du catalogue reste hors de ces tests.

DPO (responsable conformité)

IdBesoinScénarioRésultat attenduTests existantsStatut
AT-DPO-1-1 DPO-1 Étant donné un agent protégé (middleware LangChain, capacité Pydantic AI ou proxy), quand l'utilisateur écrit « Écrivez à Jean Dupont, jean.dupont@exemple.fr », alors le modèle reçoit le texte dé-identifiéLe modèle reçoit « Écrivez à <<PERSON:1>>, <<EMAIL:1>> », sans « Jean Dupont » ni « jean.dupont@exemple.fr » dans aucun messagetests/integrations/langchain/test_middleware_e2e.py::test_second_model_call_never_sees_clear_tool_args
tests/integrations/test_pydantic_ai_hooks.py::TestAroundTheModel::test_model_sees_placeholders_and_reply_is_restored
piighost-api:tests/routes/test_openai_chat.py::test_upstream_sees_tokens_reply_is_restored
couvert (valeurs « Patrick Dupont » et « patrick@acme.com »)
AT-DPO-1-2 DPO-1 Étant donné une config qui tire un groupe de secrets du catalogue, quand le message contient une clé d'API, alors la clé part en jetonLe texte dé-identifié contient un jeton du label du groupe (par exemple « <<API_KEY:1>> ») et plus la clétests/acceptance/test_dpo.py::TestSecretsLeaveAsTokens::test_an_api_key_leaves_as_a_tokencouvert (motifs OPENAI_API_KEY et AWS_ACCESS_KEY copiés de catalog:piighost/logs:b635d867, catalogue simulé, jetons « <<OPENAI_API_KEY:1>> » et « <<AWS_ACCESS_KEY:1>> »)
AT-DPO-2-1 DPO-2 Étant donné une config dont catalogs contient catalog:piighost/fr-default:<commit>, quand le texte contient l'IBAN « FR76 3000 6000 0112 3456 7890 189 » et un NIR, alors les deux sont masquésDeux jetons, ni l'IBAN ni le NIR en clairtests/config/test_detectors.py::TestRegexCatalogs::test_a_catalog_is_pulled_at_build
tests/test_catalog.py::TestFromCatalog::test_builds_a_detector_that_detects
tests/pipeline/test_pipeline.py::TestUnicodeSpaces::test_a_regex_value_typed_with_no_break_spaces_is_hidden
partiel (le chargement d'un groupe est vérifié sur un catalogue simulé, l'IBAN n'est masqué que par un motif écrit dans la configuration, le NIR jamais)
AT-DPO-2-2 DPO-2 Étant donné la même config sans le groupe qui porte l'IBAN, quand le même texte passe, alors l'IBAN reste en clair« FR76 3000 6000 0112 3456 7890 189 » figure tel quel dans la sortietests/acceptance/test_dpo.py::TestChosenGroups::test_a_removed_group_leaves_its_values_clearcouvert (catalogue simulé)
AT-DPO-2-3 DPO-2 Étant donné une config avec un motif propre à l'entreprise, écrit dans la configuration, quand le texte contient une valeur qui lui correspond, alors elle est masquée« écrivez à a@b.co vite » devient « écrivez à <<REDACT>> vite »tests/config/test_settings.py::TestLoadPipeline::test_builds_a_working_pipeline
tests/config/test_detectors.py::TestRegexCatalogs::test_catalogs_merge_in_order_then_inline_patterns
couvert
AT-DPO-3-1 DPO-3 Étant donné une liste à masquer (deny_list) contenant « Acme » et un détecteur qui ne voit rien, quand « Acme recrute » passe, alors « Acme » est masqué« <<ORG:1>> recrute »tests/pipeline/test_override_integration.py::TestBasePipelineOverride::test_deny_list_forces_a_value_the_detector_missed
tests/config/test_settings.py::TestOverrideEffect::test_deny_list_forces_a_detection
couvert
AT-DPO-3-2 DPO-3 Étant donné une liste à laisser en clair (allow_list) contenant « Paris » et un détecteur qui le voit, quand « Visitez Paris » passe, alors « Paris » reste en clair« Visitez Paris »tests/pipeline/test_override_integration.py::TestBasePipelineOverride::test_allow_list_keeps_a_false_positive_in_clearcouvert
AT-DPO-4-1 DPO-4 Étant donné un garde-fou qui détecte les adresses e-mail et un détecteur principal qui les rate, quand « Claire a écrit à leak@x.com » passe, alors rien n'est renvoyéPIIRemainingError est levéetests/pipeline/test_override_integration.py::TestBasePipelineOverride::test_a_real_leak_still_trips_the_guard
tests/config/test_settings.py::TestGuardEffect::test_detector_guard_raises_on_residual
tests/pipeline/test_pipeline.py::TestGuard::test_a_flagged_guard_raises
couvert
AT-DPO-5-1 DPO-5 Étant donné une mémoire Redis avec hacheur SHA-256 et chiffrement AES-GCM, quand le message « Je suis Claire » est mémorisé, alors la base ne contient ni le message ni la valeurAucune clé ne contient « Je suis Claire », aucune valeur stockée ne contient « Claire »tests/conversation_memory/test_redis.py::TestAtRestProtection::test_message_text_is_not_stored_in_any_key
tests/conversation_memory/test_redis.py::TestAtRestProtection::test_detections_are_encrypted_at_rest
couvert (sur RedisConversationMemory et fakeredis, pas via un fichier de config)
AT-DPO-5-2 DPO-5 Étant donné un fichier de config Redis avec hacheur et chiffreur, quand PIIGHOST_HASH_PEPPER ou PIIGHOST_CIPHER_KEY manque, alors load_thread_pipeline refuse de construireConfigError, aucun pipeline rendutests/acceptance/test_ops.py::TestSecretsFromTheEnvironment::test_a_full_redis_config_without_its_secrets_does_not_build
tests/config/test_crypto_models.py::TestHasherConfig::test_missing_pepper_is_rejected
tests/config/test_crypto_models.py::TestCipherConfig::test_missing_key_is_rejected
tests/config/test_redis_memory.py::TestRedisMemoryConfig::test_half_configured_crypto_raises_config_error
couvert
AT-DPO-6-1 DPO-6 Étant donné une conversation « t1 » qui a dé-identifié « Bonjour Claire », quand forget_thread("t1") est appelé puis deanonymize("<<PERSON:1>>", "t1"), alors la valeur n'est plus rendueLa restauration rend « <<PERSON:1>> », jamais « Claire »tests/acceptance/test_dpo.py::TestErasure::test_a_forgotten_thread_restores_nothing
tests/pipeline/test_thread.py::TestForget::test_forget_thread_clears_the_memory
tests/pipeline/test_thread.py::TestForget::test_forget_thread_drops_the_memoized_tokens
tests/conversation_memory/test_redis.py::TestForget::test_forget_purges_and_reports
couvert
AT-DPO-6-2 DPO-6 Étant donné le serveur piighost-api, quand le client envoie DELETE /v1/threads/t1, alors la conversation est effacée200 et {"messages": 2, "detections": 3}piighost-api:tests/test_app.py::test_forget_thread_returns_counts
tests/integrations/client/test_client.py::TestForgetThread::test_deletes_and_returns_a_forgotten
couvert (pipeline simulé côté serveur)
AT-DPO-7-1 DPO-7 Étant donné un pipeline avec observation_redactor, quand « Bonjour Claire ! » passe, alors les traces exportées ne contiennent que des jetonsAucun attribut de span ne contient « Claire », le span piighost.detect porte un jetontests/observation/test_pipeline_spans.py::TestRedaction::test_redactor_replaces_values_with_its_tokens
tests/observation/test_pipeline_spans.py::TestRedaction::test_redactor_removes_clear_values_from_payloads
couvert (jeton « <<REDACT>> » et non « <<PERSON:1>> », le test utilise RedactPlaceholderFactory)
AT-DPO-8-1 DPO-8 Étant donné la documentation, quand on ouvre docs/fr/dpia.md et docs/en/dpia.md, alors la page liste risques, mesures et réglagesLes deux pages existent et portent une section qui associe chaque risque à un réglageaucunabsent (critère documentaire, make lint ne contrôle que le style des pages)
AT-DPO-9-1 DPO-9 Étant donné un LLMDetector dont le modèle rend une sortie sans champ entities, quand un message passe, alors le pipeline lève une erreurUne erreur levée, aucun texte rendutests/components/detector/test_llm.py::TestDetect::test_an_unreadable_output_refuses_the_messagecouvert
AT-DPO-9-2 DPO-9 Étant donné le même détecteur avec l'échec ouvert demandé explicitement, quand le message passe, alors il part sans détection et un avertissement est journaliséLe texte inchangé, un avertissementtests/components/detector/test_llm.py::TestDetect::test_fail_open_lets_an_unreadable_output_throughcouvert
AT-DPO-10-1 DPO-10 Étant donné une correction humaine exportée sous la forme « jetons », quand le jeu de données est lu, alors il ne contient aucune valeur réelleLe jeu porte « <<PERSON:1>> », jamais « Claire »aucunabsent (fonction à concevoir)

Développeur (qui intègre piighost)

IdBesoinScénarioRésultat attenduTests existantsStatut
AT-DEV-1-1 DEV-1 Étant donné un agent create_agent existant, quand on ajoute middleware=[PIIAnonymizationMiddleware(pipeline)], alors l'agent tourne sans autre changement et le modèle ne voit que des jetonsL'appel ainvoke aboutit, aucun appel au modèle ne contient la valeur en clairtests/integrations/langchain/test_middleware_e2e.py::test_second_model_call_never_sees_clear_tool_argscouvert
AT-DEV-1-2 DEV-1 Étant donné un client OpenAI et un piighost-api doté d'un amont par défaut, quand seul base_url pointe vers /openai/v1, alors l'amont reçoit des jetons et la réponse revient restauréeL'amont reçoit « <<PERSON:1>> », le client lit « Bonjour Patrick »piighost-api:tests/routes/test_openai_chat.py::test_upstream_sees_tokens_reply_is_restored
piighost-api:tests/routes/test_upstream.py::test_upstream_default_used_when_header_absent
piighost-api:tests/routes/test_anthropic_messages.py::test_default_upstream_used_without_header
partiel (le test de bout en bout passe l'en-tête x-piighost-upstream, l'amont par défaut est testé à part, aucun test ne passe par le SDK openai)
AT-DEV-2-1 DEV-2 Étant donné une conversation où « Bonjour Claire » a été dé-identifié, quand le modèle répond « Bonjour <<PERSON:1>> », alors l'application reçoit la réponse restaurée« Bonjour Claire »tests/integrations/langchain/test_middleware.py::TestWhenInstalled::test_after_model_deanonymizes_for_display
tests/integrations/test_pydantic_ai_hooks.py::TestAroundTheModel::test_model_sees_placeholders_and_reply_is_restored
tests/pipeline/test_thread.py::TestDeanonymize::test_restores_tokens_the_pipeline_never_anonymized
couvert
AT-DEV-3-1 DEV-3 Étant donné une conversation « t1 », quand « Bonjour Claire » puis « Au revoir Claire » passent, alors la valeur garde son jeton« Bonjour <<PERSON:1>> » puis « Au revoir <<PERSON:1>> »tests/pipeline/test_thread.py::TestThreadConsistency::test_a_value_keeps_its_token_across_messages
tests/config/test_thread_pipeline.py::TestLoadThreadPipeline::test_memory_shares_placeholder_across_messages
couvert (deux messages, pas trois)
AT-DEV-3-2 DEV-3 Étant donné « Marc » dans la conversation « a » et « Claire » dans la conversation « b », quand on restaure « <<PERSON:1>> » dans la conversation « b », alors on obtient la valeur de la conversation « b »Chaque conversation numérote à partir de « <<PERSON:1>> », la restauration dans « b » rend « Claire », jamais « Marc »tests/acceptance/test_dev.py::TestThreadIsolation::test_a_token_restores_in_its_own_thread_only
tests/pipeline/test_thread.py::TestThreadConsistency::test_threads_are_isolated
tests/config/test_thread_pipeline.py::TestLoadThreadPipeline::test_threads_are_isolated
couvert (un troisième fil vide rend le jeton tel quel)
AT-DEV-4-1 DEV-4 Étant donné la stratégie FULL (par défaut), quand le modèle appelle send_mail(to="<<EMAIL:1>>"), alors l'outil reçoit la vraie valeur et son résultat repart en jetonsL'outil reçoit « patrick@acme.com », l'appel suivant au modèle ne contient ni « patrick@acme.com » ni « Patrick Dupont »tests/integrations/langchain/test_middleware_e2e.py::test_second_model_call_never_sees_clear_tool_args
tests/integrations/langchain/test_middleware.py::TestToolCalls::test_strategy_routes_each_direction
tests/integrations/test_pydantic_ai_hooks.py::TestTools::test_tool_gets_the_value_and_its_result_is_reanonymized
couvert
AT-DEV-5-1 DEV-5 Étant donné une config avec patterns = { ORDER = "CMD-\d{4}-\d{4}" }, quand « CMD-2024-0042 » passe, alors le numéro est masqué« <<ORDER:1>> » à la place du numérotests/config/test_settings.py::TestLoadPipeline::test_builds_a_working_pipelinecouvert (motif EMAIL écrit dans la configuration au lieu du numéro de commande)
AT-DEV-5-2 DEV-5 Étant donné une classe qui n'hérite de rien mais expose async detect(text) -> list[Detection], quand on la passe à AnonymizationPipeline, alors ses détections deviennent des jetonsLa sortie porte les jetons des détections de la classetests/pipeline/test_pipeline.py::TestMergeOverlap::test_the_merge_resolver_leaves_no_fragment_of_a_longer_span
tests/pipeline/test_pipeline.py::TestUnicodeSpaces::test_a_value_keeps_one_token_whatever_its_spaces
couvert (de façon incidente, par le _FixedDetector de ces tests)
AT-DEV-6-1 DEV-6 Étant donné une config valide, quand on lance piighost validate config.toml, alors la commande réussitCode de sortie 0, sortie qui commence par « OK: »tests/cli/test_cli.py::TestValidate::test_valid_config_exits_zerocouvert
AT-DEV-6-2 DEV-6 Étant donné une config où patterns est écrit pattern, quand on lance piighost validate, alors la commande échoue en nommant l'erreurCode de sortie 1, stderr qui nomme la clé fautivetests/cli/test_cli.py::TestValidate::test_invalid_schema_exits_one
tests/config/test_catalog_config.py::TestLoadConfigFromTheCatalog::test_an_invalid_configuration_names_the_reference
tests/config/test_models.py::TestDetectorConfig::test_unknown_key_is_rejected
partiel (la CLI est testée sur un type manquant et ne vérifie que « invalid configuration », la clé mal orthographiée n'est testée que dans le chargeur)
AT-DEV-7-1 DEV-7 Étant donné ExactMatchDetector({"Claire": "PERSON", "Marc": "PERSON"}), quand « Claire a vu Marc » passe, alors le texte est dé-identifié sans modèle« <<PERSON:1>> a vu <<PERSON:2>> »tests/pipeline/test_pipeline.py::TestAnonymize::test_replaces_detected_pii
tests/components/detector/test_exact.py::TestDetect::test_finds_multiple_values
couvert (l'absence de réseau n'est pas imposée par un blocage de socket)
AT-DEV-8-1 DEV-8 Étant donné la stratégie RAISE, quand le modèle répond avec « <<PERSON:9>> » jamais émis, alors la réponse est refuséeInventedPlaceholderError est levéetests/integrations/langchain/test_middleware.py::TestInventedPlaceholders::test_raise_refuses_an_invented_token
tests/integrations/test_pydantic_ai_hooks.py::TestInventedStrategy::test_an_invented_token_in_the_reply_is_refused
couvert
AT-DEV-8-2 DEV-8 Étant donné la stratégie DROP, quand le modèle répond « Bonjour <<PERSON:1>>, cc <<PERSON:9>> », alors le jeton inventé disparaît« Bonjour Claire, cc  »tests/integrations/langchain/test_middleware.py::TestInventedPlaceholders::test_kept_or_droppedcouvert
AT-DEV-9-1 DEV-9 Étant donné une conversation où « <<PERSON:1>> » vaut « Patrick », quand deanonymize_stream reçoit « Bonjour <<PER » puis « SON:1>> », alors la valeur apparaît une seule fois« Bonjour Patrick »tests/integrations/langchain/test_middleware_stream.py::test_middleware_deanonymize_stream_restores_split_token
tests/integrations/test_deidentify_stream.py::test_token_split_across_chunks_is_restored
couvert
AT-DEV-10-1 DEV-10 Étant donné le middleware LangChain, les hooks Claude Code ou le serveur d'API, quand un appel ne nomme aucune conversation, alors il est refuséMissingThreadIdError, ou 400 sur le serveurtests/integrations/langchain/test_middleware.py::TestThreadId::test_a_missing_id_raises
tests/integrations/test_claude_code_hooks.py::test_an_event_without_a_session_id_is_refused
piighost-api:tests/test_app.py::test_a_thread_route_without_a_thread_id_is_refused
couvert
AT-DEV-10-2 DEV-10 Étant donné un appel qui nomme "default", quand il passe par le middleware, alors il est acceptéLa conversation utilisée est "default"tests/integrations/langchain/test_middleware.py::TestThreadId::test_the_shared_thread_is_named_explicitlycouvert
AT-DEV-11-1 DEV-11 Étant donné la stratégie PRESERVE (par défaut), quand l'assistant écrit « C'était Napoléon. » puis l'utilisateur demande « Qu'a fait Napoléon ? », alors la valeur reste en clair« Napoléon » en clair dans les deux messagestests/pipeline/test_thread.py::TestProvenance::test_assistant_introduced_value_stays_clear
tests/pipeline/test_thread.py::TestProvenance::test_user_reference_after_assistant_stays_clear
tests/integrations/langchain/test_middleware.py::TestAssistantProvenance::test_preserve_keeps_assistant_value_clear
couvert
AT-DEV-11-2 DEV-11 Étant donné la stratégie ANONYMIZE ou IGNORE, quand l'assistant introduit la même valeur, alors elle passe en jeton, ou le message de l'assistant n'est pas analysé« <<PERSON:1>> », ou aucun appel au détecteur pour l'assistanttests/integrations/langchain/test_middleware.py::TestAssistantProvenance::test_anonymize_treats_assistant_value_as_pii
tests/integrations/langchain/test_middleware.py::TestAssistantProvenance::test_ignore_does_not_analyze_assistant_messages
couvert

Exploitant (qui déploie et fait tourner)

IdBesoinScénarioRésultat attenduTests existantsStatut
AT-OPS-1-1 OPS-1 Étant donné piighost-api serve --config hub:piighost/fr-default:e6990159, quand un client envoie POST /v1/anonymize, alors le serveur répond201 et un anonymized_text qui porte des jetonspiighost-api:tests/test_cli_serve.py::test_serve_takes_a_hub_reference
piighost-api:tests/test_app.py::test_anonymize
piighost-api:tests/test_routes_labels.py::test_the_labels_of_a_hub_catalog_come_from_the_hub
partiel (serve est testé avec uvicorn simulé, la route avec un pipeline simulé, aucun test ne relie les deux)
AT-OPS-2-1 OPS-2 Étant donné deux ThreadAnonymizationPipeline sur la même base Redis, quand l'instance A traite « Bonjour Claire » et l'instance B « Au revoir Claire » dans la conversation « t1 », alors elles s'accordentLes deux rendent « <<PERSON:1>> », B restaure « <<PERSON:1>> » en « Claire »tests/acceptance/test_ops.py::TestSharedMemory::test_two_instances_agree_on_a_threadcouvert (fakeredis partagé, une nouvelle valeur vue par B se restaure aussi depuis A)
AT-OPS-3-1 OPS-3 Étant donné une config Redis avec hacheur et chiffreur, quand le serveur démarre sans PIIGHOST_HASH_PEPPER ni PIIGHOST_CIPHER_KEY, alors il refuse de démarrerConfigError au démarrage, aucun démarrage en clairtests/acceptance/test_ops.py::TestSecretsFromTheEnvironment::test_a_full_redis_config_without_its_secrets_does_not_build
tests/config/test_crypto_models.py::TestHasherConfig::test_missing_pepper_is_rejected
tests/config/test_crypto_models.py::TestCipherConfig::test_missing_key_is_rejected
tests/config/test_redis_memory.py::TestRedisMemoryConfig::test_half_configured_crypto_raises_config_error
partiel (vérifié via load_thread_pipeline sur un fichier complet, pas au démarrage du serveur)
AT-OPS-4-1 OPS-4 Étant donné aucun API_KEY_<nom> et pas de PIIGHOST_ALLOW_ANONYMOUS, quand le serveur démarre, alors il refuseRuntimeError qui nomme PIIGHOST_ALLOW_ANONYMOUSpiighost-api:tests/test_auth.py::test_startup_fails_without_keys_by_defaultcouvert
AT-OPS-4-2 OPS-4 Étant donné le serveur, quand un client envoie un texte de 2 000 000 caractères, alors la requête est rejetée413piighost-api:tests/test_app.py::test_oversized_body_is_rejectedcouvert
AT-OPS-4-3 OPS-4 Étant donné PIIGHOST_RATE_LIMIT=minute:1, quand un client envoie deux requêtes dans la minute, alors la seconde est refusée200 puis 429, /health reste à 200piighost-api:tests/test_app.py::test_rate_limit_throttles_second_requestcouvert
AT-OPS-5-1 OPS-5 Étant donné un détecteur NER avec max_chars=20 et auto_chunk=True, quand « SECRET » arrive après 96 caractères, alors il est détectéUne détection dont le span lit « SECRET » dans le texte d'originetests/components/detector/ner/test_base.py::TestMaxChars::test_long_text_is_chunked_and_offsets_remappedcouvert (détecteur NER factice dérivé de BaseNERDetector)
AT-OPS-5-2 OPS-5 Étant donné max_chars=20 et auto_chunk=False, quand un texte de 200 caractères passe, alors le détecteur refuseTextTooLongErrortests/components/detector/ner/test_base.py::TestMaxChars::test_long_text_raises_when_auto_chunk_is_offcouvert
AT-OPS-6-1 OPS-6 Étant donné la référence catalog:piighost/notarial:2f602547, quand on appelle load_pipeline dessus, alors le pipeline se construit et tourne« écrivez à a@b.co » devient « écrivez à <<EMAIL:1>> »tests/config/test_catalog_config.py::TestLoadConfigFromTheCatalog::test_load_pipeline_builds_and_runs_itcouvert (catalogue simulé)
AT-OPS-6-2 OPS-6 Étant donné la même référence épinglée, quand on appelle load_pipeline deux fois, alors le second appel lit le cache disqueUne seule requête HTTP vers le cataloguetests/test_catalog.py::TestPullConfig::test_a_pinned_configuration_is_fetched_oncepartiel (le cache est vérifié sur pull_config, pas de bout en bout via load_pipeline)
AT-OPS-6-3 OPS-6 Étant donné une référence sans commit, quand on la charge deux fois, alors le catalogue est interrogé deux foisDeux requêtes, rien en cachetests/test_catalog.py::TestCache::test_a_moving_selector_is_never_cachedcouvert
AT-OPS-6-4 OPS-6 Étant donné un catalogue en file://, ou un catalogue qui sert un détecteur de modèle, quand on tire une référence, alors elle est refuséeCatalogUrlError ou CatalogPayloadErrortests/test_catalog.py::TestPull::test_a_catalog_that_is_not_http_is_refused
tests/test_catalog.py::TestPull::test_a_model_detector_is_refused
couvert
AT-OPS-7-1 OPS-7 Étant donné une mémoire en processus construite sans réglage, quand 10 001 conversations sont écrites, ou qu'une conversation reste un jour sans message, alors la plus ancienne ou l'inactive est oubliéeLa première conversation ne rend plus ses détectionstests/conversation_memory/test_in_memory.py::TestBounding::test_the_default_store_caps_its_thread_count
tests/conversation_memory/test_in_memory.py::TestBounding::test_the_default_store_expires_an_idle_thread
tests/config/test_thread_pipeline.py::TestInMemoryConfig::test_a_bare_section_is_bounded_like_the_store
couvert

Utilisateur (de l'application construite avec piighost)

IdBesoinScénarioRésultat attenduTests existantsStatut
AT-USER-1-1 USER-1 Étant donné une application LangChain, Pydantic AI, LlamaIndex ou passant par un proxy, quand le modèle répond « Bonjour <<PERSON:1>> », alors l'utilisateur lit la vraie valeur« Bonjour Jean Dupont », jamais « Bonjour <<PERSON:1>> »tests/integrations/langchain/test_middleware.py::TestWhenInstalled::test_after_model_deanonymizes_for_display
tests/integrations/test_pydantic_ai_hooks.py::TestAroundTheModel::test_model_sees_placeholders_and_reply_is_restored
tests/integrations/llama_index/test_query_engine.py::TestQuery::test_anonymizes_query_and_restores_answer
piighost-api:tests/routes/test_openai_chat.py::test_upstream_sees_tokens_reply_is_restored
piighost-api:tests/routes/test_anthropic_messages.py::test_upstream_sees_tokens_reply_is_restored
couvert (le test LlamaIndex est ignoré sans l'extra)
AT-USER-1-2 USER-1 Étant donné Claude Code avec les hooks piighost, quand l'assistant répond avec un jeton, alors l'utilisateur lit la vraie valeurLa réponse affichée ne contient aucun jetonaucunabsent (limite connue, handle_hook ne traite que UserPromptSubmit, PreToolUse et PostToolUse)
AT-USER-2-1 USER-2 Étant donné une conversation qui cite « Claire » puis « Claire et Marc », quand le modèle répond « Merci <<PERSON:1>> et <<PERSON:2>>. », alors chaque personne garde son jeton et sa valeur« <<PERSON:1>> et <<PERSON:2>> » puis « Merci Claire et Marc. »tests/pipeline/test_thread.py::TestThreadConsistency::test_a_new_value_gets_the_next_number
tests/pipeline/test_thread.py::TestDeanonymize::test_restores_tokens_the_pipeline_never_anonymized
couvert (on ne peut pas tester de façon déterministe que le modèle ne confond pas deux jetons)
AT-USER-3-1 USER-3 Étant donné un agent LangChain ou Pydantic AI, quand l'assistant envoie un e-mail à « <<EMAIL:1>> », alors l'outil reçoit la vraie adresseL'outil reçoit « patrick@acme.com »tests/integrations/langchain/test_middleware_e2e.py::test_second_model_call_never_sees_clear_tool_args
tests/integrations/test_pydantic_ai_hooks.py::TestTools::test_tool_gets_the_value_and_its_result_is_reanonymized
couvert
AT-USER-3-2 USER-3 Étant donné un proxy OpenAI ou Anthropic, quand le modèle renvoie un appel d'outil avec {"who": "<<PERSON:1>>"}, en flux ou non, alors le client reçoit l'argument restauré{"who": "Patrick"} dans les quatre cas (OpenAI et Anthropic, en flux et non)piighost-api:tests/routes/test_rewrite.py::test_deanonymize_chat_response_restores_content_and_tool_args
piighost-api:tests/routes/test_anthropic_shape.py::test_restorer_restores_tool_input_json_safely
piighost-api:tests/routes/test_anthropic_messages.py::test_tool_result_anonymized_and_tool_use_restored
partiel (le cas OpenAI en flux n'est ni restauré ni testé)
AT-USER-4-1 USER-4 Étant donné le décodeur de flux, quand « Bonjour <<PER » arrive, alors seul « Bonjour  » est émis, puis « SON:1>>! » émet « Claire! »Aucun morceau émis ne contient « <<PER »tests/components/placeholder/test_streaming_async.py::TestFeed::test_a_split_token_is_held_then_completed
tests/components/placeholder/test_streaming.py::TestFeed::test_split_delimiter_is_held
couvert (un flux coupé au milieu d'un jeton rend « <<PER » à la fin, par choix, voir tests/integrations/test_deidentify_stream.py::test_trailing_incomplete_token_is_flushed)
AT-USER-4-2 USER-4 Étant donné un proxy en flux, quand l'amont coupe « <<PERSON:1>> » en deux événements, alors le client ne reçoit jamais de fragmentLe flux reçu contient « Patrick » et pas « <<PERSON »piighost-api:tests/routes/test_openai_stream.py::test_stream_restores_a_token_split_across_chunks
piighost-api:tests/routes/test_anthropic_messages.py::test_stream_restores_token_split_across_deltas
couvert
AT-USER-5-1 USER-5 Étant donné « Paris » dans la liste à laisser en clair, quand « Visitez Paris » passe, alors la ville reste lisible« Visitez Paris »tests/pipeline/test_override_integration.py::TestBasePipelineOverride::test_allow_list_keeps_a_false_positive_in_clearcouvert
AT-USER-5-2 USER-5 Étant donné un groupe générique du catalogue, quand « Réunion le 12/03/2026 » passe, alors la date reste en clair« Réunion le 12/03/2026 »aucunabsent (dépend du contenu des groupes du catalogue, jamais chargé dans les tests de la bibliothèque)
AT-USER-6-1 USER-6 Étant donné un détecteur qui rate « Acme », quand l'utilisateur ajoute la détection, alors « Acme recrute » part en jeton et se restaure« <<ORG:1>> recrute », restauré en « Acme »tests/pipeline/test_thread_hitl.py::TestAnonymizeCorrected::test_adds_a_missed_value
tests/pipeline/test_thread_hitl.py::TestAnonymizeCorrected::test_added_value_is_deanonymizable_thread_wide
couvert
AT-USER-6-2 USER-6 Étant donné « Paris » masqué à tort, quand l'utilisateur retire la détection, alors ce message part en clair et les suivants ne changent pas« Visitez Paris », puis « Allez à <<LOCATION:1>> »tests/pipeline/test_thread_hitl.py::TestAnonymizeCorrected::test_drops_a_false_positive
tests/pipeline/test_thread_hitl.py::TestAnonymizeCorrected::test_correction_is_local_to_the_message
couvert
AT-USER-6-3 USER-6 Étant donné une liste à masquer qui contient la valeur, quand l'utilisateur la retire, alors elle reste masquéeUn jeton, pas la valeurtests/pipeline/test_override_integration.py::TestThreadPipelineOverride::test_deny_list_trumps_a_hitl_drop
tests/pipeline/test_override_integration.py::TestThreadPipelineOverride::test_allow_list_trumps_a_hitl_add
couvert

Bilan

ProfilCouvertPartielAbsentTotal
DPO141217
DEV162018
OPS103013
USER91212
Total497460

Manquants

Classés par risque, en commençant par ceux qui peuvent causer une fuite.

  1. AT-USER-3-2 (partiel) : arguments d'outil restaurés dans le flux OpenAI. Défaut fonctionnel connu, l'outil reçoit un jeton.
  2. AT-DPO-2-1 (partiel) : un vrai groupe du catalogue (IBAN, NIR) masque ses valeurs. Seul un catalogue simulé est chargé.
  3. AT-DPO-10-1 (absent) : la forme de stockage des corrections humaines, fonction à concevoir.
  4. AT-OPS-3-1 (partiel) : le refus sans secrets est vérifié via load_thread_pipeline, pas au démarrage de piighost-api.
  5. AT-OPS-1-1 (partiel) : serve sur une référence du catalogue puis POST /v1/anonymize dans un seul test.
  6. AT-DEV-6-2 (partiel) : piighost validate sur une clé mal orthographiée, avec la clé nommée dans le message.
  7. AT-DEV-1-2 (partiel) : le SDK openai avec seulement base_url changé.
  8. AT-OPS-6-2 (partiel) : le cache disque vu à travers load_pipeline.
  9. AT-USER-5-2 (absent) : une date n'est pas masquée par un groupe générique. Dépend du contenu des groupes, à tester dans le dépôt du catalogue, piighost-hub.
  10. AT-USER-1-2 (absent) : réponse affichée restaurée sous Claude Code, impossible tant qu'aucun hook ne réécrit la réponse.
  11. AT-DPO-8-1 (absent) : contenu de la page AIPD, contrôlable par un test de documentation.

Comportements testés absents de l'inventaire

ComportementTests qui le gardentBesoin ou critère proposé
Un jeton tapé par l'utilisateur ne restaure pas la valeur d'un autretests/components/anonymizer/test_span_anonymizer.py::TestAnonymize::test_user_typed_token_cannot_hijack_a_restore
tests/components/anonymizer/test_span_anonymizer.py::TestDeanonymize::test_no_prefix_collision_when_one_token_prefixes_another
Critère de DPO-1  : « un message qui contient <<PERSON:2>> ne fait pas apparaître la valeur de la deuxième personne »
Une valeur tapée avec des espaces insécables reste masquée et garde son jetontests/pipeline/test_pipeline.py::TestUnicodeSpaces::test_a_regex_value_typed_with_no_break_spaces_is_hidden
tests/pipeline/test_thread.py::TestUnicodeSpaces::test_a_value_keeps_its_token_across_spacings_and_messages
Critère de DPO-1
Deux détections qui se chevauchent ne laissent aucun fragment en clairtests/pipeline/test_pipeline.py::TestAnonymize::test_overlapping_detections_are_resolved_by_default
tests/pipeline/test_pipeline.py::TestMergeOverlap::test_the_merge_resolver_leaves_no_fragment_of_a_longer_span
Critère de DPO-1
L'historique, le contenu en blocs et les arguments d'outil déjà passés repartent en jetonstests/integrations/test_pydantic_ai_hooks.py::TestMultiTurn::test_prior_turn_pii_is_not_leaked_to_the_model
tests/integrations/langchain/test_middleware.py::TestWhenInstalled::test_before_model_anonymizes_block_content
tests/integrations/langchain/test_middleware.py::TestToolCalls::test_before_model_reanonymizes_clear_tool_call_args
Critère de DPO-1  : « aucun tour suivant ne renvoie une valeur en clair »
Le texte analysé par LLMDetector ne peut pas sortir de sa zone de données (injection)tests/components/detector/test_llm.py::TestDetect::test_a_data_tag_in_the_text_cannot_close_the_data_regionPoint de vigilance de DPO-1
Tracer en clair, ou stocker en clair dans Redis ou une base SQL réseau, émet PIIGhostSecurityWarningtests/observation/test_pipeline_spans.py::TestClearTextTracingWarning::test_warns_without_a_redactor_or_acknowledgment
tests/conversation_memory/test_redis.py::TestRedisPlaintext::test_round_trips_without_crypto
tests/conversation_memory/test_sqlalchemy.py::TestWarning::test_networked_dialect_without_crypto_warns
Critère d'OPS-3
Avec des clés configurées, une route protégée sans jeton Bearer répond 401piighost-api:tests/test_auth.py::test_protected_route_401s_without_bearer_when_auth_enabledCritère d'OPS-4
Le proxy OpenAI oublie la conversation éphémère d'une requête sans en-tête de conversationpiighost-api:tests/routes/test_openai_chat.py::test_ephemeral_thread_is_forgottenCritère de DPO-6 , au titre de la minimisation
piighost anonymize dé-identifie un argument ou l'entrée standard sans fichier de configtests/cli/test_cli.py::TestAnonymize::test_default_detector_anonymizes_an_argument
tests/cli/test_cli.py::TestAnonymize::test_reads_stdin_on_dash
un besoin développeur à créer (dé-identifier sans fichier de configuration)
Le middleware refuse un pipeline dont les jetons ne sont pas reconnaissablestests/integrations/langchain/test_middleware.py::TestFactoryContract::test_a_pipeline_without_a_recognizer_is_refusedCritère de DEV-1
BridgeDetector lit les positions dans l'unité du runner (UTF-16 côté navigateur)tests/components/detector/ner/test_bridge.py::TestDetect::test_offsets_are_read_in_the_runner_unitun besoin développeur à créer (détection locale dans le navigateur)