entity-core (0.2.3)
Installation
pip install --index-url https://git.it.iaos.cc/api/packages/mapfarm/pypi/simple/ --extra-index-url https://pypi.org/simple entity-coreAbout this package
Brique de résolution d'entités (CDC-ENTITY-CORE-v1, refondue ADR-074) — un résolveur fail-closed sur un canon REÇU (référentiel de tenants, instantané JSON), jamais embarqué. Niveau plateforme / stateless.
entity-core
Brique de résolution d'entités — la sémantique de l'identité d'entité (normalisation
fail-closed, alias, marque → propriétaire, garde de cohérence), appliquée à un canon reçu.
Toutes les briques de la plateforme (iaos · corpus · harvest · factory · cockpit · fin-core ·
client-core · catalog-core) l'appellent pour résoudre une désignation d'entité, au lieu de
re-coder leur propre liste.
Spécifiée par CDC-ENTITY-CORE-v1 (docs/), refondue par ADR-074 (dépôt iaos,
2026-09-12) : l'entité est une donnée du tenant, jamais une constante du produit.
v0.1 → v0.2, rupture voulue. La v0.1 embarquait le canon d'un tenant particulier (six entités, leurs alias,
harvest → hold) : toute instance du produit — y compris celle vendue à un tiers — portait cet organigramme et refusait le sien. La v0.2 ne connaît aucun nom. Les symboles de moduleCANON,normalize,is_entity,resolve_marque_ownern'existent plus : un consommateur qui ne reçoit pas de canon ne compile pas — c'est le cliquet.
Niveau plateforme / stateless. Aucune base, aucun réseau, aucune dépendance. Ce qui reste de la v0.1 : stateless (le canon est un argument) et offline (un instantané JSON remplace le canon embarqué). Ce qui tombe : global — le canon de la holding n'est pas celui du produit.
Principes (non négociables — CDC §5/§6)
| # | Règle |
|---|---|
| R1 / P4 | Une seule sémantique de résolution : entity-core. Aucune liste re-codée par dépôt — et aucune liste dans la brique. |
| R2 | Toute frontière d'entrée d'entité normalise via un Canon (route, RAG, INSERT, allow-list). |
| R3 / G2 | Fail-closed : forme inconnue → REFUS ; jamais de devinette ni de pass-through. Un canon vide refuse tout. |
| G1 | Un canon reçu incohérent (alias vers une non-entité, marque qui est aussi une entité, snapshot d'une autre version) est refusé à la construction. |
| G3 | Typo non bénie : toute faute → refus, jamais d'alias ; correction à la source (le référentiel). |
| R4 / P2 | Stateless / plateforme : pas de données par app, pas de cache caché — le consommateur décide d'où vient le canon et quand il le rafraîchit. |
| R7 | Une forme connue mais hors axe entité (marque, société, socle) est refusée par normalize avec sa raison ; une marque se résout par resolve_marque_owner. |
Le canon — d'où il vient
from entity_core import Canon
# 1. Depuis le référentiel de tenants (iaos : tables `tenants` + `entity_aliases`).
# L'appelant filtre sur SON tenant ; la brique ne connaît pas les tenants.
canon = Canon.from_rows(
tenants=[ # company_id|slug, kind, is_entity, display_name?
{"company_id": "siege", "kind": "internal", "is_entity": True},
{"company_id": "ops", "kind": "internal", "is_entity": True},
{"company_id": "socle", "kind": "internal", "is_entity": False, "display_name": "socle doc"},
{"company_id": "client-a", "kind": "external"}, # ignoré : pas dans le canon d'un autre
],
aliases=[ # alias, slug, kind? ∈ {alias (défaut), marque}
{"alias": "operations", "slug": "ops"},
{"alias": "brand-x", "slug": "siege", "kind": "marque"},
],
)
# 2. Depuis un instantané JSON (consommateurs offline : snapshot versionné dans le dépôt,
# régénéré depuis GET /api/iaos/v1/tenants).
canon = Canon.from_json(open("canon.json").read())
open("canon.json", "w").write(canon.to_json()) # déterministe, clés triées
# 3. En clair (tests, fixtures).
canon = Canon(entities={"siege", "ops"}, aliases={"operations": "ops"},
marques={"brand-x": "siege"}, non_entities={"acme": "société"})
# 4. Le canon d'une instance qui ne s'est pas encore déclarée : tout est refusé.
canon = Canon.empty()
Usage
from entity_core import assert_canon
from entity_core.errors import NotAnEntity, UnknownEntity
canon.normalize("Operations") # → "ops"
canon.normalize("ops") # → "ops" (idempotent)
canon.is_entity("brand-x") # → False (marque : hors axe entité)
canon.resolve_marque_owner("brand-x") # → "siege"
canon.normalize("opss") # UnknownEntity [R3] — typo : refus, jamais d'alias
canon.normalize("acme") # NotAnEntity [R7] — société, avec sa raison
canon.normalize("brand-x") # NotAnEntity [R7] — « utiliser resolve_marque_owner »
assert_canon(canon, ["siege", "operations"]) # True | CanonDivergence [G4] (CI rouge)
Canon est immuable (__slots__, mappings figés), comparable et hachable par contenu.
Installation (registre PyPI Gitea)
pip install --index-url https://git.it.iaos.cc/api/packages/mapfarm/pypi/simple/ entity-core==0.2.0
# ou, build OFFLINE souverain : pip install --no-deps ./vendor/entity-core
Développement
make install # pip install -e ".[test]"
make lint # ruff check + ruff format --check
make test # pytest — unitaires purs, aucune base
make build # sdist + wheel
Publication : workflow publish-entity-core.yml (workflow_dispatch) après bump de version.