Metadata-Version: 2.5
Name: iaos-sdk
Version: 0.3.2
Summary: SDK Python des produits pour le noyau iaos — connecteurs, notifier, corpus, lecture, runs hébergés, topologie déclarée (ADR-078 §11 P2.16).
Author: Holding MAPFARM
License: Proprietary
Requires-Python: >=3.11
Requires-Dist: httpx<1,>=0.27
Provides-Extra: corpus
Requires-Dist: pyyaml>=6; extra == 'corpus'
Provides-Extra: test
Requires-Dist: pytest>=8; extra == 'test'
Requires-Dist: pyyaml>=6; extra == 'test'
Description-Content-Type: text/markdown

# iaos-sdk — SDK Python des produits pour le noyau iaos

ADR-078 §11 P2.16 (« SDK publiés sur la forge ») : une source au lieu de copies vendorées
produit par produit. Version 0.3.2 :

| Module | Ce qu'il fait | Remplace |
|---|---|---|
| `iaos_sdk.connecteurs` | appels de connecteurs par le noyau (`POST /api/connectors/<nom>/call` : DataForSEO, Keepa, Google Trends, Arctic Shift), run courant transmis, erreurs claires (`NoyauConnecteurError`) | `graphs/common/core/iaos_gateway.py` de harvest et fdm (0.1.0) |
| `iaos_sdk.notifier` | courriel et Slack d'exploitation par la capacité « notifier » (`POST /api/iaos/v1/notifier`), une seule fois (`cle_idempotence`), `categorie` transactionnel / diffusion | `graphs/common/core/iaos_notifier.py` de harvest et fdm (0.1.0) |
| `iaos_sdk.corpus` | socle documentaire : RAG (`query`, `query_producer`, `query_scoped`, budgets de grounding), ingestion (simple ou gouvernée), propositions DRAFT, génération gouvernée, vecteurs, lecture de documents (par réf, résolution stricte, contenu exact, historique) ; lecture STRICTE opt-in (`list_documents` / `get_document_content` avec `strict=True` → `CorpusIndisponibleError`, motif public) — 0.3.2 | `corpus_client.py` de harvest et fdm (copies identiques) et de la factory (copie divergente) — 0.2.0 |
| `iaos_sdk.lecture` | lecture du noyau : runs, événements et flux SSE d'un run, coûts, missions, tenants, registres ; clé scopée par tenant ; lectures de MESURE strictes (`LectureImpossibleError`) ; **lecture applicative gouvernée** (`lecture_applicative`, `registre_lecture` — contrat 1.11.0, ADR-049 D2 : un bloc vivant de Rédaction, donnée d'un produit de la holding avec sa provenance T2) | `iaos_read_client.py` de la factory — 0.2.0 ; lecture applicative — 0.3.1 |
| `iaos_sdk.runs` | protocole des runs hébergés (ADR-075) : ouvrir, raconter un nœud, battre, clore, relire — sync et async, lève en cas d'échec ; rattachements d'un document (`mission_id`, `parent_run_id`, `budget_usd`, `open_mission` — CDC-APPS-BUREAU L1) et reprise d'une porte au nom d'une personne (`reprendre_run` / `areprendre_run` : `decided_by` ou `Remote-User`, clé de relais `hitl:approve`) — 0.3.1 | le fil HTTP de `app/services/iaos_client.py` (mapbox) et `apps-api/iaos_client.py` (iaos-apps) — 0.2.0 |
| `iaos_sdk.topologie` | topologie DÉCLARÉE d'un agent (contrat 1.8.0) : `publier_topologie(agent_key, nodes, edges)` (`PUT /api/iaos/v1/agents/{key}/topology`, versionné, idempotent), `depuis_langgraph(compiled)` (sérialise `get_graph()` comme le noyau), `publier_langgraph(agent_key, compiled)` — sync et async | le `GET /topology/{key}` que chaque hôte devait exposer pour le relais du noyau — 0.3.0 |

Restent hors du SDK, à dessein : `src/agents/meta_corpus_client.py` du cockpit (pas une copie :
lecteur strict à contrat — `claims_blocked == 0`, ACTIVE, provenance — propre au rapport
d'orchestration) ; la POLITIQUE des clients de runs (allowlist d'agents, entité imputée, clé LLM
du run, dégradation) qui reste au produit.

### Points de variation du client corpus

Ce qui différait entre les copies est réglé par le produit, une fois, dans son module d'alias
(`iaos_sdk.corpus.regler(...)`) — jamais deviné par le SDK :

| Réglage | Défaut | harvest / fdm | factory |
|---|---|---|---|
| `sur_claims_bloques(node, n, motifs)` | aucun | `node_events_client.post_claim_blocked` | — |
| `est_entite_plateforme(company_id)` | aucun → entité (repli sûr) | `company_id in entities.ACCEPTED_ENTITIES` | — |
| `ingestion` | `"enveloppe"` (`ingested`/`pending`/`error`) | `"enveloppe"` | `"socle"` (réponse brute, `skipped`/`refused`/`error` + motif du corps) |
| `controle_ingestion(company_id, contenu)` | aucun | — | gate W14 `corpus_tenancy.check_content_ownership` |
| `provenance_inline` | `False` | — | `True` (`[source: titre · id: réf]` en tête de morceau) |
| `entite_defaut` | `hold` | — | `ENTITE` |
| `agent_onboarding` | `harvest-onboarding` | — | — |

## Installation — référence directe, jamais par nom

Le registre PyPI de la forge publie le paquet (`.gitea/workflows/publish-iaos-sdk.yml`,
déclenchement manuel). On l'installe **par URL + empreinte**, jamais par nom avec
`--extra-index-url` (dependency confusion : un tiers pourrait publier `iaos-sdk` sur
pypi.org) :

```
iaos-sdk @ https://git.it.iaos.cc/api/packages/mapfarm/pypi/files/iaos-sdk/0.3.2/iaos_sdk-0.3.2-py3-none-any.whl#sha256=<empreinte de la page simple/iaos-sdk/>
```

Monter de version = changer l'URL ET l'empreinte (outil : `~/.cache/iaos-outils/epingler_forge.py`).

## Adoption dans un produit sans toucher aux appelants

L'ancien module devient un ALIAS du module du SDK ; les imports existants et les
`monkeypatch` des tests visent le même objet module :

```python
# graphs/common/core/iaos_gateway.py — désormais fourni par iaos-sdk
import sys

import iaos_sdk.connecteurs as _sdk

sys.modules[__name__] = _sdk
```

Un module à points de variation se règle AVANT l'alias :

```python
# corpus_client.py — désormais fourni par iaos-sdk
import sys

import iaos_sdk.corpus as _sdk


def _claims_bloques(node, blocked, reasons):
    from graphs.common.node_events_client import post_claim_blocked  # lu à l'appel (tests)

    post_claim_blocked(node, blocked, reasons)


_sdk.regler(sur_claims_bloques=_claims_bloques)
sys.modules[__name__] = _sdk
```

## Rendre un agent VISIBLE dans la fenêtre (topologie + étapes)

La console (« La fenêtre › Le run vivant ») dessine le graphe d'un agent et allume le nœud que
son run est en train de franchir. Deux gestes côté produit :

1. **Déclarer le graphe**, une fois au démarrage (ou au déploiement) — clé du projet HÔTE de
   l'agent (celui dont la `base_url` préfixe son `service_url`) :

   ```python
   from iaos_sdk import topologie

   # StateGraph LangGraph : le graphe tel que compilé.
   topologie.publier_langgraph("menu_composer", graphe_compile)
   # Fonction ordinaire : les étapes et leurs enchaînements, à la main.
   topologie.publier_topologie(
       "menu_composer",
       ["contexte", "composition", "controle"],
       [("__start__", "contexte"), ("contexte", "composition"),
        ("composition", "controle"), ("controle", "__end__")],
   )
   ```

   Republier le même graphe ne crée rien (empreinte) ; un graphe changé devient la version
   suivante (historique : `GET /api/iaos/v1/agents/{key}/topology/versions`).

2. **Raconter les étapes** de chaque run — `iaos_sdk.runs.publier_noeud(…, node="composition",
   seq=n)` à la SORTIE de chaque étape (mêmes `id` que le graphe déclaré).

Sans déclaration, la fenêtre reconstruit une topologie OBSERVÉE des étapes publiées par les
derniers runs (badge « observé sur N runs ») : raconter les étapes suffit déjà à être visible ;
déclarer donne le graphe complet dès le premier run, branches jamais empruntées comprises.

## Configuration (environnement)

`IAOS_API_URL` (défaut `http://iaos-api:8000`), `IAOS_PROJECT_KEY` (clé projet, `X-Iaos-Key`),
`IAOS_CONNECTOR_TENANT` (défaut historique `harvest`), `CONNECTEURS_VIA_NOYAU`,
`IAOS_NOTIFIER_COMPANY` (défaut `hold`), `MESSAGES_VIA_NOYAU` ; corpus : `CORPUS_API_URL`,
`CORPUS_DOC_API_URL`, `CORPUS_API_KEY`, `CORPUS_DRAFT_KEY`, `CORPUS_TENANT_ID`,
`CORPUS_HOLDING_COMPANY` ; lecture : `IAOS_READ_TIMEOUT`, `IAOS_TENANT_KEYS`,
`IAOS_TENANT_KEY_<TENANT>`, `IAOS_TENANT_KEY`, `IAOS_SERVICE_KEY` — voir l'en-tête de chaque
module. `iaos_sdk.runs` ne lit pas l'environnement : base et clé sont passées par l'appelant.

## Journal des versions

| Version | Date | Changement |
|---|---|---|
| `0.3.2` | 2026-10 | `corpus.list_documents` et `corpus.get_document_content` : paramètre `strict=False` opt-in — en mode strict, une panne (non configuré, injoignable, délai, HTTP, réponse illisible) lève `corpus.CorpusIndisponibleError` dont le message est un motif public, sans URL, clé ni corps de réponse ; la classe survit à un `importlib.reload`. `get_document_content` encode le `doc_id` dans le chemin. Rien ne change sans `strict`. |
| `0.3.1` | 2026-10 | `runs.corps_ouverture` / `ouvrir_run` / `aouvrir_run` acceptent `mission_id`, `parent_run_id`, `budget_usd`, `open_mission` (posés seulement s'ils sont fournis) ; `runs.reprendre_run` / `areprendre_run` (`POST /runs/{id}/resume` : décision, `payload`, `decided_by`, `feedback`, `Remote-User`) ; `lecture.lecture_applicative` et `lecture.registre_lecture` (contrat noyau 1.11.0). Rien ne change pour un appelant de 0.3.0. |
| `0.3.0` | 2026-10 | `iaos_sdk.topologie` : topologie déclarée d'un agent (contrat 1.8.0). |
| `0.2.0` | 2026-09 | `iaos_sdk.corpus`, `iaos_sdk.lecture`, `iaos_sdk.runs` (runs hébergés, ADR-075). |
| `0.1.0` | 2026-09 | `iaos_sdk.connecteurs`, `iaos_sdk.notifier`. |

Publication (manuelle, Actions › « Publish iaos-sdk », `publish-iaos-sdk.yml`) : monter `version`
dans `pyproject.toml` ET `__version__`, puis lancer le workflow — tests, build, `twine upload` sur
le registre PyPI de la forge. Les produits épinglent la version par URL + sha256 (ci-dessus).

## Développement

```
pip install -e ".[test]" ruff
ruff check src tests && ruff format --check src tests && pytest -q
```
