Les quatre paquets @ng-org sont a leur derniere version publiee : le
refresh ne deplace rien (verifie par dist-tag, liste complete des
versions et pnpm outdated). Le travail reel etait la reconciliation
laissee par le refresh precedent.
Deux constats consignes dans le Result :
- l'arbre etait installe par bun, pas par pnpm, avec un store .bun
portant encore web alpha.13, shex-orm alpha.8 et alien-deepsignals
alpha.11 a cote des courants ; danger d'instance unique latent,
reconcilie par une reinstallation pnpm
- le checkout epingle de nextgraph-rs (213338f6) declare orm alpha.18,
soit trois versions derriere les paquets compiles : toute citation de
source decrit une version que ce depot n'utilise plus
Piege a retenir : web, shex-orm et alien-deepsignals publient aussi un
0.1.2 nu, que semver classe AU-DESSUS des alpha alors qu'il date de
2025. Ce sont les pins exacts qui l'ecartent.
Quatre portes vertes, plus les mesures live : orm-live-read 5 passed /
0 failed (pret en 13ms), suite e2e polyfill 43 passed / 0 failed.
Les quatre paquets @ng-org sont deja a leur derniere version publiee
(web alpha.14, orm alpha.21, shex-orm alpha.9, alien-deepsignals
alpha.13) : le refresh ne deplace rien. Ce qui restait faux, c'est la
doctrine, laissee en arriere par le refresh precedent.
Le point de fond : le checkout epingle de nextgraph-rs (213338f6)
declare orm alpha.18, donc la source citee est TROIS versions derriere
les paquets contre lesquels ce depot compile. La ou les deux divergent,
les leaves nomment desormais les deux.
Faits corriges, tous verifies dans node_modules :
- les versions installees annoncees en tete de leaf (alpha.19/13/11)
- OrmSubscription est renomme RdfOrmSubscription dans alpha.21, fichier
compris (connector/RdfOrmSubscription.ts)
- alpha.21 exporte removeObject(graphNuri, subjectIri), absent de la
source ; sa propre documentation se contredit sur sa portee
- getOrCreate prend un RdfOrmConfig, plus un Scope nu, et l'instance
gagne mode/isReady/addChangeListener/nextPage/previousPage
- Scope.graphs est REQUIS dans alpha.21, et les deux axes lisent [] en
sens INVERSE : graphs [] = ensemble vide, subjects [] = aucun filtre.
La table le dit explicitement, c'est le piege deja paye une fois
- la lecture ORM live DEMARRE contre le deploiement (5 passed, 0 failed,
pret en 13ms) ; le lien mort vers bug_useshape-live-read-needs-a-newer
-broker disparait, ainsi que le "exit non-zero attendu" de la suite
Ce qui n'est PAS affirme : que le where/orderBy/pageSize d'alpha.21
atteigne le moteur. A 213338f6 start_orm construit encore son config a
partir de json!({}) derriere un TODO, donc la richesse est exprimable au
niveau 3 et jetee au niveau 1. Non mesure, donc non ecrit.
Le contrat publie et l'api-contract etaient traites avec le retrait lui-meme ;
restaient les documents qui decrivaient readUnion comme la primitive de
lecture du paquet, ou qui nommaient le module disparu.
Ce que les lecteurs apprennent maintenant : lire un document, c'est
docs.sparqlQuery ancre dessus, et lire plusieurs documents, c'est cet appel
une fois par document -- avec la decision sur une lecture qui echoue ecrite
au point d'appel. Deux consequences sont dites plutot que decouvertes : une
lecture refusee leve au lieu de rendre une liste vide, et les sujets sous le
prefixe reserve urn:ng-eventually: reviennent dans le resultat, a l'appelant
de les ignorer -- sans aucun gain de droits, les deux registres qu'un
document ordinaire porte etant l'adresse de sa boite et, pour un document
public, la cle que son magasin sert deja a qui la demande.
Les briefs dates ne sont pas touches : ils decrivent un etat de juillet, et
le reecrire falsifierait ce qui avait ete decide alors.
readUnion etait N requetes ancrees plus une politique de lot : une lecture
qui echoue avalee en reponse vide, un document dont on ne detient pas la cle
ecarte au lieu d'etre refuse, un regroupement en sacs de proprietes, et le
filtrage du prefixe reserve. Rien en amont ne repond a cette composition, et
son annotation citait sparql_query -- une citation exacte attachee a une
affirmation fausse. Une composition ne s'aligne pas sur une fonction.
Ce qui reste est ce qu'il y avait dessous : docs.sparqlQuery ancre sur un
document, qui va deja chercher la cle servie par le magasin public -- donc
une reference nue suffit toujours pour lire un document public, sans rien
changer a useShapeQuery. Lire plusieurs documents, c'est cet appel une fois
par document, et la tolerance a une lecture qui echoue revient a l'appelant,
la ou l'amont la laisse : une requete ancree sur un depot que le verifier ne
detient pas rejette.
Cote src, l'unique lecture interne restante devient read-document.ts : un
document, une requete ancree, precedee de l'ouverture du depot -- barriere
que index-deposit consulte pour distinguer une absence d'un depot non
synchronise. Le harnais e2e et l'exemple composent la leur, avec leur catch
visible au point d'appel.
Version majeure : 1.0.0-dev.8.
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.
watchShape attrapait l'échec de résolution des documents, journalisait, et
passait une liste vide. Or une barrière sur zéro document est franchie
trivialement — la surface publiait donc { data: [], isPending: false,
isSuccess: true }, octet pour octet l'instantané « synchronisé et vide ». La
seule distinction pour laquelle ce module existe était celle qu'il détruisait.
Un échec de résolution devient isError, jamais isSuccess. Et comme un
observable ne peut pas dé-émettre, l'état « je ne sais plus » conserve la
DERNIÈRE lecture qui a répondu, avec isSuccess à faux : une liste vide n'est
jamais la réponse d'un échec.
Le canal choisi est l'état de chargement, parce que c'est celui qu'une
application lit déjà pour distinguer « en attente » de « vide ». La troisième
valeur ne lui coûte aucun vocabulaire neuf.
Vérification faite en amont plutôt qu'en supposant : readyPromise n'aurait pas
aidé — construit avec resolve seul, rien ne le rejette, et l'échec
d'orm_start_graph n'est qu'un console.error. Le « je n'ai pas pu savoir » de la
cible EST son « toujours en attente ». La classification invention tient, et les
annotations le disent désormais.
C'est le dernier membre connu de cette famille dans le polyfill : après
connectedUser, resolveAccount, userInbox, readInboxCapPairs et
listMyEntityDocs, la couche réactive était le dernier endroit où un échec se
présentait comme une absence.
Une application a rapporté quatre appels simultanés sur un même document
enregistrant trois inboxes. Le contrat garantissait l'inverse. En cherchant, on
en a trouvé DEUX autres, indépendantes, qui produisent le même dégât durable :
le propriétaire surveille une inbox pendant que les dépôts arrivent dans une
autre.
La concurrence. openDocumentInbox ne partageait rien avec userInbox — module
différent, registre propre, aucune coalescence. Reproduit pire que rapporté :
quatre appels donnaient QUATRE inboxes. Une carte en vol par (détenteur,
document), et le corps déplacé pour que l'invariant soit porté par la
composition plutôt que par la position d'une vérification.
La limite est nommée plutôt que cachée : deux onglets ne partagent aucune carte,
chacun lit, chacun ne trouve rien, chacun frappe. Ce n'est pas réparable ici —
une branche est en ajout seul, et ça ne se réconcilie pas après coup, le
propriétaire lisant sa branche User quand un déposant lit l'adresse publiée du
document. Le contrat porte donc une garantie positive ET une non-garantie.
La page froide. readInboxCapPairs était le seul lecteur de store sans barrière,
correct uniquement parce qu'une autre fonction s'exécutait avant lui à la
connexion. Une dépendance d'ordre, pas une garantie portée par la lecture : sur
une page froide il lisait le store privé non synchronisé, répondait « aucune
inbox » et en frappait une seconde. Un seul appel, aucune concurrence. La
barrière est désormais dans la lecture, et elle ne coûte rien aux chemins
connectés, la connexion ayant déjà ouvert les trois stores.
Et la lecture qui manquait. readSynced donnait la garantie, readForDocument
l'adressage, pas leur intersection — si bien que matérialiser des dépôts
obligeait une application à résoudre une adresse d'inbox elle-même, ce que le
contrat lui interdit explicitement. inbox.readSyncedForDocument la lui épargne.
Elle traverse deux dépôts, l'adresse vivant sur l'en-tête du document et les
dépôts sur l'inbox — franchir la barrière sur la seule inbox ne réparait rien.
Au passage, le compteur d'identifiants de la doublure était par page : une page
rechargée refrappait le même identifiant PAR-DESSUS une inbox existante,
aliasant deux dépôts en silence. Il est monotone.
listMyEntityDocs rendait la liste des documents même quand la lecture des clés
échouait. Vu de l'appelant, une liste dont les documents ne s'ouvrent pas est
INDISCERNABLE d'une liste dont ils s'ouvrent : rien ne signale la différence
jusqu'à une lecture ultérieure qui revient vide, la cause étant alors loin
derrière.
C'était un choix délibéré — ne pas transformer un appel publié en levée, la
liste étant déjà en main à ce moment-là. C'est précisément ce qui en faisait une
demi-vérité plutôt qu'un raccourci. Et c'était le dernier membre connu de la
famille qui a produit une panne chez une application cette semaine.
Les deux lectures sur lesquelles l'appel repose remontent désormais : la branche
Main dit quels documents sont là, la branche Store dit ce qui ouvre chacun. Un
tableau vide signifie donc que ce compte n'a rien créé dans cette portée, et
jamais que le store n'a pas été lu.
readUserStore avalait le même échec pour son propre compte ; son autre appelant,
ownsDocument, garde le comportement actuel par un catch explicite et documenté —
toutes ses réponses étant des refus, il échoue en fermeture, ce qui est la
règle qu'e32b6d0 avait posée.
C'est un changement de comportement d'un appel publié, donc le contrat le dit,
et docs/api-contract.md aussi.
Au passage, deux citations pourries corrigées en citant un SYMBOLE plutôt qu'une
ligne — types.rs:4251 désignait DialogRequest et non Link, index.d.ts:138
désignait const ng et non le type NG. Les douze autres références numériques du
voisinage ont été vérifiées : aucune n'avait bougé.
98 annotations posées à côté des déclarations, et un test qui les exige sur la
surface publiée. Elles portent trois choses : le niveau qui répond, la référence
amont, et la catégorie parmi les cinq.
La cinquième est celle qui manquait : declared-not-wired, quand la cible DÉFINIT
la forme et ne la câble pas. Neuf symboles en relèvent, dont readLinks — que
j'avais classé « notre invention » en raisonnant depuis l'absence, alors que
c'est le meilleur alignement disponible.
Les références citent un SYMBOLE, jamais une ligne : trois citations du document
avaient déjà pourri. Cinq corrections au passage, toutes vérifiées à la source —
un chemin ORM qui n'existe pas, deux plages de lignes fausses, et surtout
docs.* et subscribeDoc étiquetés PASSTHROUGH alors qu'ils sont alignés : nos
noms, plus un argument jamais transmis. La sémantique survit à la migration,
les sites d'appel non, et la nuance disparaissait sous une étiquette trop
flatteuse.
Le test échoue à l'annotation retirée, à la catégorie mal orthographiée, et à
une invention qui prétendrait citer une référence — vérifié en cassant les
trois. Il a aussi attrapé un défaut en lui-même : le gabarit de format placé
dans index.ts se faisait analyser comme une annotation.
La classification couvre l'interne qui prétend ressembler à la cible — tout
emulated-verifier — et exclut ce qui ne le prétend pas. La faute d'origine
portait sur une fonction non exportée ; n'être pas publié n'a protégé personne.
Quatre symboles ont résisté et sont annotés avec leur catégorie dominante, la
seconde nommée dans la note plutôt que lissée.
J'ai classé readLinks comme notre invention parce qu'AddLinkV0 n'est construit
nulle part en amont. C'était raisonner depuis l'absence — ce que cette règle
interdit, et que je citais dans la même session. La bonne lecture était que la
cible a DÉFINI la forme et ne l'a pas câblée : nous l'implémentons, ce qui est
le meilleur alignement disponible.
Les deux arrivent ensemble et l'absence est la plus bruyante, ce qui lui fait
gagner des arguments qu'elle devrait perdre. D'où la règle : lire la
déclaration, et laisser le silence autour ne rien dire.
Elle gagne sa place dans la taxonomie, parce que la confondre avec « invention »
fait croire qu'on est plus loin de la cible qu'on ne l'est — et un lecteur
ultérieur, voyant « notre idée », se sentira libre de la changer.
Cinq catégories désormais, et une provenance à côté de chaque symbole plutôt que
dans un document qui dérive : le niveau qui répond, la référence amont, la
catégorie. Un test l'exige sur la surface publiée. Il ne peut pas vérifier que
la citation est vraie, et c'est acceptable : une citation se réfute en quelques
secondes, une prose non — docs/api-contract.md affirmait un passthrough « 1:1 »
faux sur deux points et un décompte de membres erroné, sans que rien n'oblige
personne à aller voir.
La classification vaut aussi pour le code non publié qui PRÉTEND ressembler à la
cible : la faute qui motive tout ceci a été commise sur une fonction interne, et
n'être pas exportée n'a protégé personne.
Deux défauts ont été livrés et rapportés par une application, sans que la suite
applicative puisse les voir. Le trou était précis : aucun parcours ne faisait
revenir un PROPRIÉTAIRE après qu'il a ouvert son document aux messages. Le
parcours 3 fait ouvrir Alice et revenir Bob ; Alice, elle, ne se reconnecte
jamais. Il s'arrêtait une reconnexion trop tôt.
Alice écrit deux notes publiques, en ouvre une aux messages, Bob y dépose en la
nommant, puis Alice revient. Elle doit être reconnue, lire la note qu'elle a
faite en ne tenant que sa référence, POUVOIR ENCORE Y ÉCRIRE, et trouver le
message laissé en son absence.
L'écriture compte autant que la lecture : un document public survit à une
relecture après rechargement, sa clé étant retrouvée dans le store, et n'échoue
qu'à l'écriture. Un parcours qui se contenterait de relire aurait manqué la
moitié.
Et il a été vérifié contre le code d'AVANT le correctif, dans un worktree
jetable : les quatre vérifications échouent, sur
« docs.sparqlQuery: refused — the connected user does not hold this document's
cap ». Ce refus apparaît une fois dans chaque journal d'avant et zéro fois dans
les trois d'après. Un parcours qui passe des deux côtés ne prouve rien — c'est
exactement comme ça que ce trou avait survécu.
La reconnexion est vraie : nouvelle page, réalisme JS neuf, donc tous les caches
de module disparaissent pendant que le portefeuille reste intact. Rien n'est
pré-injecté — la référence qu'Alice colle, elle l'a lue sur son propre écran.
Total de vérifications : 34 → 39.