Le régime des contrats est devenu bidirectionnel : chaque partie rédige sa
position dans son dépôt et tire celle de l'autre. Le fournisseur écrit son
engagement, chaque consommateur écrit sa déclaration — ce qu'il appelle
vraiment, sous quelles contraintes, et avec quelles frictions.
Ce dernier point change nos usages : la section Frictions est le chemin par
lequel un consommateur expose un problème, le canal hors-bande ne portant que le
signal. Les huit points que Festipod nous a remontés auraient dû arriver par là,
et notre réponse appartient à l'engagement plutôt qu'à un échange qui s'évapore.
Trois interfaces existaient, une seule était documentée. Elles ont maintenant
leur dossier et leurs deux voix : la surface du polyfill, ng-e2e-helpers, et la
couche d'indexation. On a écrit les engagements dont nous sommes l'auteur et les
déclarations des consommateurs qui sont à nous — délibérément AUCUN
usage_festipod : ce n'est pas notre dépôt, et décider à leur place ce qu'ils
consomment viderait le mécanisme de son sens.
Et la Change policy s'inverse. Elle disait « ce paquet n'offre pas de stabilité
sémantique ». Refuser un schéma ne ralentit pas le mouvement — ça retire au
consommateur le seul outil pour le gérer, alors qu'on faisait déjà le travail
d'une publication versionnée sans lui en donner l'étiquette.
Semver, donc, et les majeurs sont le cas NORMAL : une surface qui converge vers
une cible qu'elle n'atteint pas encore casse souvent, et c'est le signal vrai.
Le texte dit les déclencheurs plutôt que le nom du schéma — retirer un symbole
publié ou resserrer un appel est un majeur, ajouter est un mineur. Version
pleine sur main, pré-version sur une branche : Festipod peut épingler
aujourd'hui sans qu'on lui promette du stable, et sans qu'on ait à lui retirer
son adresse à la fusion.
Au passage, la signature publiée de watchShape était fausse — elle annonçait un
argument unique là où l'appel en prend deux. Rapporté par Festipod, confirmé
indépendamment.
Le nom @ng-eventually/sdk entrait en collision avec le SDK de NextGraph, dont
ce paquet est justement un polyfill. Impossible d'écrire « le SDK » sans lever
l'ambiguïté à chaque phrase — et le contrat publié, lu par une application,
était le pire endroit pour laisser traîner ça.
packages/sdk → packages/polyfill, @ng-eventually/sdk → @ng-eventually/polyfill,
contract_sdk-surface → contract_polyfill-surface, e2e/sdk-entry.ts →
e2e/polyfill-entry.ts, docs/sdk-reference.md → docs/polyfill-reference.md.
Les occurrences de « SDK » qui désignent celui de NextGraph restent intactes,
y compris les chemins dans nextgraph-rs (sdk/js/orm, sdk/js/web). Le tri s'est
fait occurrence par occurrence, pas par substitution.
Le contrat énonce désormais son identité en une phrase : « This package is a
polyfill of NextGraph's SDK. »
La feuille testait ce que l'APPELANT apprendrait. Le propriétaire du projet a énoncé la
règle plus large : rester au plus près de NextGraph, et n'admettre aucune implémentation
qui en diverge — que l'appelant s'en aperçoive ou non.
L'ordre des deux questions est ce qui a manqué. Sur la fusion de `readUnion`, posée en
premier, « l'appelant devra-t-il désapprendre ? » ne tranchait pas : « une entité par
document » est une bonne pratique par ailleurs, alors que désapprendrait-il au juste ? Le
raisonnement a piétiné des heures là-dessus. Posée en premier, « la cible fait-elle ça ? »
a demandé un regard : le niveau 1 rend les sujets réels, et l'ORM du niveau 3 porte `@id`
ET `@graph` sur chaque objet en fabriquant le premier quand on l'omet. Divergence, fin.
« Devra-t-il désapprendre ? » reste, mais mesure la gravité d'une divergence inévitable —
jamais son autorisation. Et un tell est ajouté, celui qui a produit ce défaut : une
recommandation que le code impose au lieu de la guider, en rendant l'autre disposition
invisible.
Amorce le système `concept` dans ce dépôt et ouvre `app-contract` — la frontière
entre cette bibliothèque et les applications qui la consomment.
C'est un contrat **inter-dépôts** et ce dépôt en est le FOURNISSEUR : les applications
vivent ailleurs et tireront `sdk-surface` d'ici. D'où le type `contract_`, ses cinq
sections obligatoires, et l'inscription dans `.project/contracts.yaml` — c'est
l'inscription qui publie.
Trois feuilles :
- **`contract_sdk-surface`** — l'engagement, écrit du point de vue de l'appelant.
Ce qu'il peut tenir pour acquis : permissif en entrée et précis en sortie ; toute
référence rendue est NUE, aucun appel ne rend jamais de clé ; lire est la possession,
écrire est la propriété ; donner à lire est un seul acte et le destinataire n'appelle
rien ; un dépôt s'adresse à une inbox, jamais à un document ; `ensureIdentity()` est
toute la connexion ; et `configure` est le seul appel qu'il supprimera.
Ce qu'il ne doit PAS tenir pour acquis, dit aussi crûment : aucune confidentialité,
rien de « par lecteur » sur un document public, aucune révocation, aucune écriture
déléguée, et les références ne voyagent que dans un déploiement.
- **`rule_would-the-caller-unlearn-it`** — le test qui décide de tout : est-ce que
ceci ferait apprendre à l'appelant quelque chose qu'il devra DÉSAPPRENDRE ? Avec les
deux tells que la revue de ces jours-ci a rendus concrets : une exception nommée
cesse d'en être une dès qu'on la publie, et un symbole gardé parce qu'il était là
n'est pas une décision.
- **`knowledge_what-an-app-deletes-at-migration`** — les deux destins d'un symbole
publié, le cas intermédiaire d'`ensureIdentity` (substance jetée, site d'appel
conservé), et le fait que la liste de suppression n'est plus portée par un chemin
d'import depuis la fusion des entrées : une garantie mécanique remplacée par une
garantie documentaire, dont seule la moitié est tenue par un test.
Le vocabulaire du concept fixe trois termes que ce projet a déjà payé cher :
`reference` (jamais « lien »), `ReadCap`, `polyfill-era`. `lint` est conformant.
Reste à décider : ce dépôt n'a pas de `CLAUDE.md` racine, donc l'`AGENTS.md` généré
n'est chargé nulle part.