Commit Graph

10 Commits

Author SHA1 Message Date
Sylvain Duchesne ff78a70f14 feat!: le paquet crée un index et lui ajoute une référence, rien de plus v4.0.0 2026-08-21 19:56:11 +02:00
Sylvain Duchesne c2f9ff4674 feat!: la lecture quitte le contrat, un index se lit comme un document v3.0.0 2026-08-21 15:49:59 +02:00
Sylvain Duchesne ebaae15baf docs: dire l'effet et non le canal, et n'exiger le NURI en dur que d'un index global v2.0.1 2026-08-20 14:47:55 +02:00
Sylvain Duchesne e2ed970cbd feat!: curer n'est plus un appel, c'est ce que fait le traitement de l'inbox v2.0.0 2026-08-20 11:04:31 +02:00
Sylvain Duchesne 2ce2113157 fix: le polyfill est un peer, pas un chemin vers la copie de travail d'un développeur v1.0.1 2026-08-17 11:48:17 +02:00
Sylvain Duchesne 821ea997fe docs: dire pourquoi le tag est nu, pour qu'il cesse de l'être au bon moment 2026-08-17 10:53:32 +02:00
Sylvain Duchesne aeb8c7d157 docs: l'engagement de la couche d'indexation, et ses deux déclarations
Le dépôt gagne sa doctrine et ses contrats, dans le régime bidirectionnel.

Il publie son engagement — ce qu'un consommateur peut attendre de l'indexation —
et déclare ce qu'il consomme lui-même, du polyfill et de ng-e2e-helpers. Les
deux déclarations sont écrites depuis les appels réels, pas depuis ce que la
surface offre : un usage non déclaré est la faute du consommateur en cas de
rupture, et une surface offerte mais non déclarée reste librement modifiable.

Version 1.0.0, pas 0.1.0 : sous semver, 0.x ne promet rien du tout, donc le
majeur ne porte son signal qu'à partir de 1. Ce dépôt étant sur main, c'est une
version pleine et non une pré-version.
v1.0.0
2026-08-17 10:10:23 +02:00
Sylvain Duchesne f4050b95c0 test(e2e): éprouvé contre un vrai broker, et ce qu'on y apprend
69 tests unitaires, trois rounds d'auto-critique, et rien n'avait jamais tourné
contre un vrai broker. Sept parcours, vingt vérifications, à travers
ng-e2e-helpers — rien de réimplémenté.

La forme d'écriture est ACCEPTÉE par oxigraph, établie en relisant le document
et non parce que la mise à jour n'a pas levé : après curation, readUnion rend
deux sujets, celui de l'index et celui de l'objet de Bob, l'entrée portant sa
valeur. C'était l'une des deux inconnues.

L'autre est REPRODUITE, et c'est un défaut : un openInbox qui échoue en cours de
createIndex laisse un document orphelin. L'appel rejette et ne rend rien, mais
le document existe dans le store public du propriétaire, porte le descripteur,
et refuse les dépôts. Le paquet n'expose pas openInbox, donc il ne peut ni le
réparer ni le supprimer — orphelin permanent. Injecté pour être atteint : rien
de ce que contrôle un appelant ne fait échouer un vrai openDocumentInbox.

Et quatre endroits où la suite unitaire prouve moins qu'elle ne l'annonce, tous
vérifiés. Le plus net : les treize tests d'adaptateur ne chargent JAMAIS le vrai
polyfill. Preuve dure — la copie installée avait perdu un fichier qu'importe
surface/inbox.ts, et 69 sur 69 passaient quand même. Aucun des deux côtés n'a
tort ; c'est l'affirmation « l'adaptateur fonctionne » qui n'était pas testée.
Rien n'a été affaibli, l'e2e est ce qui la teste enfin.

Les trois autres sont de la même nature — une doublure trop faible plutôt qu'un
code faux : ses NURI n'ont pas la forme réelle, elle n'écrit pas la machinerie
que le vrai document porte, et étant une seule Map elle ne peut par construction
jamais révéler un retard de cohérence.

Trois exécutions, 27/27 chacune, autour de 58 secondes, aucune reprise.
2026-08-17 10:02:13 +02:00
Sylvain Duchesne 75378fc5a4 test: rendre la suppression inexécutable plutôt que détectée
Trois trouvailles adverses survivaient à leur premier correctif. Elles avaient
la même faiblesse : ce qui prouve le code n'exerçait pas le code.

L'adaptateur n'était pas testé dans son comportement — six méthodes sur sept
pouvaient être vidées avec la suite verte. Une doublure de polyfill en mémoire
les exerce désormais toutes : elle RÉPOND au lieu de rendre des constantes, et
elle EXÉCUTE le SPARQL, avec un analyseur qui n'accepte qu'un INSERT DATA ancré
de triplets littéraux. Les six vidages ont été vérifiés rouges, entre 2 et 10
échecs chacun.

Et la garde anti-suppression change de nature. Elle n'instrumentait que deux
méthodes, et cinq contournements passaient — dont un littéral coupé placé dans
readDeposits, qui est le chemin de la prochaine fonctionnalité. Maintenant le
moteur REFUSE la requête : « expected the keyword INSERT, found DELETE ». La
suppression n'est plus détectée, elle est inexécutable — ce qui ne se contourne
pas par une écriture plus habile.

Une nouvelle méthode est couverte par construction, sans liste à tenir : le test
lit Object.keys du port. Vérifié — une huitième méthode sans pilote fait rougir.

Le README disait qu'une entrée ne change jamais après sa création, alors qu'une
valeur plus petite l'écrase. Remplacé par ce qui est vrai — un objet déjà indexé
n'est jamais relu — avec le cas reproduit. Et « ne fait que grandir » dit
désormais que la suppression n'a JAMAIS été construite, pas qu'elle a été
retirée : celui qui en aura besoin doit le lire, pas le déduire d'une fonction
manquante.

Enfin la forme d'écriture s'aligne sur celle du polyfill — l'écriture ancrée
sans clause GRAPH, le document nommé une seule fois comme ancre. buildInsertTriple
ne prend plus de graphe, donc elle ne peut plus dériver.
2026-08-17 09:23:24 +02:00
Sylvain Duchesne 469346aef3 feat: un index est un document ordinaire, et il ne fait que grandir
Nouveau dépôt, séparé de ng-eventually-js à dessein : NextGraph n'aura jamais
de notion d'index, à aucun niveau. Ce n'est donc pas un échafaudage en attente
d'un amont, c'est une construction au-dessus — et le polyfill ne doit rien
apprendre de l'indexation. Sa boîte de réception reste générique et transporte
des dépôts opaques ; ce qu'un dépôt VEUT DIRE se décide ici.

La frontière tient par un seul fichier : polyfill-adapter.ts est le seul import
runtime du polyfill, tout le reste est écrit contre NextGraphPort. Les six
entrées utilisées sont toutes publiées dans contract_polyfill-surface.

Un index est un document ordinaire du store public de son créateur. Ce qui en
fait un index, c'est qu'une application référence sa NURI dans son propre code.
Il déclare, sur son propre sujet, le champ qu'il indexe — un prédicat, les
objets étant du RDF. « Indexé par une date » n'est pas un genre d'index à part :
c'est un index dont le champ est un prédicat de date, et les entrées ressortent
dans l'ordre chronologique parce qu'ISO-8601 se trie comme une chaîne.

UN DÉPÔT EST UNE RÉFÉRENCE NUE, RIEN D'AUTRE. Pas d'opération, pas de référence
à l'index (l'adresse de la boîte l'identifie déjà), pas de copie de la valeur
indexée. Le curateur résout la référence et REGARDE ; ce que dit l'objet fait
foi, pas ce que dit le déposant. C'est la forme qu'utilise déjà l'amont, où une
SocialQueryRequest porte une référence et le destinataire compose son propre
SPARQL. Une charge utile portant une opération serait un droit d'écriture sur
le document d'autrui, puisque n'importe qui peut déposer.

Corollaire : aucune vérification de propriété, et il n'en faut aucune. Déposer
une référence n'obtient rien de plus que ce que le propriétaire aurait fait —
ce qui permet à un passant qui remarque une entrée manquante de relancer la
vérification.

UN INDEX NE FAIT QUE GRANDIR. Rien n'en est jamais retiré, par personne. C'est
cette limitation qui rend l'histoire des pannes triviale : la seule écriture
étant un ajout, une référence qui ne se résout pas — objet disparu, illisible,
ou broker muet — ne peut jamais signifier que « pas ajouté cette fois ». Rien
n'a à distinguer une absence d'un échec, donc rien ne peut se tromper là-dessus.
C'est le défaut corrigé en 8c8ade7 et e32b6d0, où une lecture qui ÉCHOUAIT
ressortait comme une absence.

Mais LE CHEMIN D'ÉCRITURE N'A JAMAIS ÉTÉ LE PROBLÈME. Trois tours de revue
adverse ont cassé la garantie cinq fois, sans jamais rien supprimer — toujours
en LECTURE :

- lire une entrée exigeait EXACTEMENT une valeur : un sujet en portant deux se
  lisait comme ABSENT, et un second addLiteralProperty faisait disparaître une
  entrée. Deux curations concurrentes produisent exactement cet état ;
- la même règle sur le champ déclaré était pire : un unique ajout d'un second
  INDEX_FIELD rendait le descripteur illisible et emportait TOUTES les entrées,
  définitivement ;
- un seul sujet non-NURI levait hors de entriesOf et rendait d'un coup toutes
  les vraies entrées illisibles ;
- un champ nommé « constructor » renvoyait une fonction héritée de
  Object.prototype et faisait planter la curation pour tous les dépôts restants ;
- et le correctif du deuxième point CORROMPAIT l'index à la place : « la plus
  petite l'emporte » changeait le champ alors que les entrées déjà écrites
  gardaient l'ancien, donc read() rendait une liste unique « triée par valeur »
  mêlant deux propriétés. Une réponse fausse et silencieuse, pire qu'un arrêt.

Ce qui tient maintenant : une entrée existe dès UNE valeur, la plus petite
l'emporte, de façon déterministe. La lecture est tolérante entrée par entrée et
ne lit que les propriétés propres. Lire un index demande seulement « est-ce un
index ? » ; le champ n'est exigé que pour CURER, et une déclaration ambiguë
refuse bruyamment au lieu de choisir. Ce refus est définitif : c'est le prix
honnête de l'absence de suppression, et le message le dit au lieu de suggérer
un réessai.

La garde anti-suppression portait elle-même le défaut qu'elle dénonçait. Elle
listait des noms d'export, puis a scanné la source : quatre contournements
passaient encore (COPY DEFAULT TO GRAPH, un mot-clé caché derrière le retrait
des commentaires, DELETE{ sans espace, un littéral coupé en concaténations).
Un motif sur la SOURCE se contourne toujours. La vraie garde EXÉCUTE désormais
l'adaptateur contre un enregistreur et relit chaque requête émise — les quatre
y échouent. Le scan de source reste, dégradé en simple fil-piège.

Le double de test construisait props par affectation simple alors que l'amont
fait (props[p] ??= []).push(o) : il était plus permissif que la réalité, et un
test s'appuyait dessus pour affirmer un résultat que la production ne peut pas
produire. Il construit maintenant props à l'identique.

La leçon vaut d'être gardée : « rien ne supprime » est une affirmation sur le
chemin d'ÉCRITURE, et un invariant sur ce qu'un lecteur VOIT doit se vérifier
aussi sur le chemin de LECTURE.

Un échec reste un échec et reste VISIBLE : inoffensif n'est pas invisible. Toute
référence non résolue ressort en `unresolved` dans le rapport et est signalée ;
la règle « lecture vide = non résolu » vit dans resolution.ts, à part de l'I/O,
parce que dans l'adaptateur aucun test ne l'atteignait — et la supprimer laissait
la suite verte pendant qu'un échec était classé « l'objet n'a pas le champ ».

Questions ouvertes, documentées dans le README plutôt que tranchées : un objet
sans le champ déclaré, un objet à plusieurs valeurs, une entrée qui ne change
jamais après coup, quelle valeur garde une entrée disputée, comment un index se
remet d'une déclaration ambiguë, des dépôts jamais retirés.

59 tests, tsc --noEmit vert. Aucune exécution contre un vrai broker.
2026-08-16 16:21:48 +02:00