Commit Graph

25 Commits

Author SHA1 Message Date
Sylvain Duchesne 98e46effc2 fix: le resultat d-une commande de page suit la meme regle que tout le reste
Balayage execute sur les deux proxys apres le correctif precedent : aucun membre
de la vue de collection ne rend plus la cible, mais une commande de page de
l-enveloppe qui rendrait la collection la rendait BRUTE. `nextPage` est declare
`() => void` et ne rend rien aujourd-hui, donc ce n-est pas un defaut vivant -
c-est la meme justification jamais executee, sur l-autre proxy, et c-est
exactement le motif qu-il fallait casser. Elle s-execute toujours ; seul son
retour est tenu a la regle.
2026-09-24 19:03:10 +02:00
Sylvain Duchesne c5de1cea35 fix: une mutation ne rend ni n-atteint ce que la vue cache
`Set.prototype.add` rend THIS pour permettre le chainage - EXECUTE, pas deduit,
sur le `Set` nu et sur le vrai `DeepSignalSet`, qui rendent tous deux la
collection. Les trois mutations etaient transmises liees a la cible sur le motif
ecrit juste au-dessus : "elles prennent un objet et rendent void ou un booleen,
donc elles ne laissent rien fuir". Faux pour `add`, et `add` est le PREMIER
membre que l-exemple de documentation du SDK emploie. Toutes les entrees etaient
filtrees et la sortie rendait la collection entiere.

Les deux autres rendaient bien ce que le motif disait, et AGISSAIENT au-dela de
la vue : `delete` retirait un objet dont `has` refuse meme de confirmer
l-existence - les deux se contredisaient - et `clear` vidait la collection que
toutes les identites partagent.

Une seule regle : une mutation atteint la collection dessous, mais ne rend ni
n-atteint ce que la vue cache. `add` rend la VUE, `delete` n-agit que sur ce que
la vue montre, `clear` vide la vue. L-ecriture atteint toujours le document.

Le motif commun aux cinq fuites de cette famille est une JUSTIFICATION ECRITE
SUR UNE VALEUR DE RETOUR JAMAIS EXECUTEE. Chacune etait raisonnable, aucune
n-avait ete jouee.

Aussi : la forme ordonnee rend des index la ou un Set rend des objets
(`keys`/`entries`/`forEach`), et le faux partage reproduit desormais le
`isLoading` LIVRE - inverse de sa propre declaration. L-inversion est amont ;
un faux plus correct que la cible recree l-aveuglement que cette tache enleve.
2026-09-24 19:00:19 +02:00
Sylvain Duchesne e86e249a5a fix: le descripteur d-un membre repond ce que le lire repond
Trouve en rejouant l-adversaire sur le correctif precedent, et mesure :
`Object.getOwnPropertyDescriptor(resultat, "data")` rendait la collection BRUTE,
et `(..., "subscription")` le handle que `get` refuse. Cet appel ne passe pas
par le piege `get` - ni l-etalement, ni `Object.entries`, ni `JSON.stringify`,
ni une inspection console. Une deuxieme porte sur la meme piece, ouverte par un
piege que personne n-avait ecrit. Le meme trou existait sur la vue de collection
pour la trappe dunder.

La forme ordonnee dit combien d-objets elle porte a QUATRE endroits : `length`,
`Object.keys`, `in`, et le descripteur de chaque index. Un seul etait filtre,
donc le compte de ce que le porteur ne peut pas lire ressortait par les trois
autres, et le descripteur rendait les objets eux-memes.

Les quatre pieges appellent maintenant une seule fonction `read`, pas trois
copies de la regle : une frontiere faite de `switch` paralleles est une
frontiere qui derive. Et l-index nomme une position dans ce qui est LISIBLE,
donc la forme ordonnee sert son porteur au lieu de refuser.
2026-09-24 18:35:22 +02:00
Sylvain Duchesne 3a8941159f fix: le filtre de lecture couvre l-enveloppe que le SDK renvoie vraiment
Le `useShape` publie enveloppait le resultat du hook dans le filtre ecrit pour
une COLLECTION. Depuis que `@ng-org/orm` renvoie un `UseShapeResult` autour du
set, `[...resultat]` et `.size` levaient, et `data`, `promise` et `subscription`
revenaient BRUTS - chacun tous les objets de toutes les identites, sans que rien
ne casse. Aucun faux de ce depot ne renvoyait la forme livree, donc aucune garde
ne pouvait le voir.

Deux vues au lieu d-une : celle de la collection, et celle de l-enveloppe que
`surface/use-shape.ts` applique. `data` et `promise` sont filtres, `subscription`
est refuse (c-est le handle de l-ORM, que cette surface ne publie pas), les deux
commandes de page passent.

Le defaut par TYPE et non par une liste de noms : un primitif ne peut atteindre
aucun objet et passe, un objet ou une fonction refuse tant qu-il n-existe pas de
forme filtree ici. C-est pourquoi `isLoading` n-est nomme nulle part, et pourquoi
un membre qu-une version suivante ajoutera est deja tranche. `size` et `length`
comptent ce que le porteur peut lire.

`UseShapeLike` etait `(...args: any[]) => any`, ce qui est la raison pour
laquelle les deux typechecks restaient a 0 pendant que la fuite etait ouverte :
aucune declaration ne disait ce que le hook rend. Il rend `ShapeResult`, releve
sur le `.d.ts` installe, et le vrai `reactUseShape` y est assignable sans cast.

Le faux partage `test/use-shape-fake.ts` renvoie la forme livree et c-est celui
que les deux gardes injectent - unitaire et e2e. La garantie du contrat et le
test qui l-epingle arrivent ensemble ; `1.0.0-dev.11`, majeur.
2026-09-24 18:27:02 +02:00
Sylvain Duchesne 139d21843a fix: les trois citations que les paquets installes invalidaient
Recherche menee sur les 94 citations vers engine/ et sdk/, en comparant
l'arbre cite aux artefacts publies. Onze citations de niveau 2
(doc_subscribe, sparql_update, sparql_query, doc_create) sont
identiques de part et d'autre, ainsi que ShapeType, Schema et
DeepSignalSet. Trois ne l'etaient pas.

- Scope : la revision citee declare `graphs?` OPTIONNEL,
  @ng-org/orm@0.1.2-alpha.21 le rend requis - et c'est le paquet
  installe qui est reexporte ici.
- BaseType : la revision citee garde "@graph" en commentaire derriere un
  TODO, @ng-org/shex-orm@0.1.2-alpha.9 le declare.
- NG : le compte de membres disait 88 en neuf endroits, dont un marque
  VERIFIED et un autre annoncant une mesure contre alpha.14. Le paquet
  installe en declare 90 - 84 fonctions et 6 classes RDF - et deux des
  quatre-vingt-dix, graph_orm_next_page et graph_orm_previous_page, sont
  absents de l'arbre cite. Le compte vit desormais a un seul endroit,
  avec la version a laquelle il appartient ; ailleurs il disparait, parce
  que la phrase disait "tous les membres" et que le chiffre n'y ajoutait
  qu'une occasion de perimer.

Les deux premieres reprennent la forme que RdfOrmConfig et
OrmSubscriptionHandle portaient deja : une clause finale nommant la
version publiee la ou elle diverge de la revision citee.
2026-09-24 15:05:50 +02:00
Sylvain Duchesne 8fe8c052da docs: dire par situation ce qu'un appelant voit changer, pas les deux ensemble
Le texte disait que les deux situations passaient de `isSuccess` a
`isPending`. Verifie : seul le second `configure()` publiait un succes
(vide ou perime). Sur un refus tardif du courtier la lecture etait deja
retenue - ce qui change est la sortie, la nouvelle tentative se
re-souscrit au lieu d'etre bloquee pour la session.
2026-09-24 13:03:33 +02:00
Sylvain Duchesne de9b8a0d67 feat!: une seule reponse a « ce document est-il ouvert dans cette session »
Trois reponses coexistaient et pouvaient se contredire : un ensemble de
souscriptions bootstrap et une carte d'etat de barriere dans `open-repo`, et les
auditeurs du fan-out dans `surface/subscribe`. Les deux premieres survivaient a
la disparition de la troisieme, parce que `resetDocSubscriptions()` - ce que fait
le `configure()` publie - largue ses auditeurs sans rien dire a personne.

Il n'en reste qu'une, l'etat de l'unique souscription reelle du document, exposee
par `docChannel(nuri)`. `open-repo` n'enregistre plus que l'auditeur qu'il a pose
- « est-ce que j'ecoute encore », rien de plus - et derive `getSyncState` a chaque
lecture, si bien qu'aucune reponse ne peut survivre au canal qu'elle decrit.
Disparaissent : `held`, `syncState`, `inFlight`, `settleInFlight` et le compteur
de generation, remplace par l'identite de l'enregistrement.

Les deux echecs prouves deviennent des tests qui echouent sans le changement :
un second `configure()` apres une synchro reussie (`"synced"` au lieu de
`"timed-out"`, zero re-souscription, et `isSuccess` cote application), et un
etablissement encore en cours quand la couche de souscription est demontee - cas
que le canal `onSetupFailed` ne peut pas rattraper, puisqu'il ne rapporte que la
tentative encore attendue.

La ligne qui ne casse pas : un document synchronise qui ne contient rien repond
toujours un succes a zero objet. `1.0.0-dev.10`, les deux non-garanties
nommees sont retirees et la garantie livree porte le test qui l'epingle.
2026-09-24 12:57:43 +02:00
Sylvain Duchesne 52ac4d974e docs: le contrat descend a ce que le code tient vraiment, et T01.j est scindee
La sixieme revue a refute la reparation du tour 6 et a trouve la famille sur
laquelle nous nous etions engages a scinder. `held` n'est pas le registre unique :
il y en a trois, et celui que personne n'avait compte est `fanOuts` dans
`subscribe.ts`. `configure()`, qui est PUBLIE, vide les auditeurs sans le dire a
`open-repo` ; et `held.set()` s'execute avant meme que `establish` atteigne
`doc_subscribe`. `held` veut donc toujours dire « on a demande » - le sens plus
lache pour lequel j'avais supprime `opened`. J'ai remplace l'un des deux registres
qui se contredisaient ; il y en avait trois.

Aucun code touche ici. Le defaut de registre part dans T01.m, avec ses deux echecs
prouves comme tests d'acceptation.

Le contrat dit desormais ce que le code tient : la moitie « le document a-t-il
repondu » vaut DANS UNE SESSION ordinaire, et pas apres un second `configure(...)`
ni quand le courtier refuse un abonnement tardivement. Les deux situations sont
nommees en non-garanties avec leur cause et le renvoi vers T01.m. La moitie
« peut-on joindre le document » n'a pas cette reserve : elle est verifiee deux fois
contre un vrai courtier, et la retirer ramenerait un succes vide silencieux
partout, ce qui est pire qu'une garantie partielle honnetement enoncee.

Pas de changement de version : aucun comportement publie ne bouge, seule la
justesse de sa description.

Et une faute de compte-rendu consignee, parce que c'est un motif : trois fois j'ai
affirme qu'un changement avait reduit ou simplifie quelque chose, trois fois la
mesure disait le contraire. Regle desormais mecanique - toute affirmation
structurelle porte son `wc -l` ou son `--numstat` dans la meme phrase, sinon elle
ne s'ecrit pas.
2026-09-24 11:55:12 +02:00
Sylvain Duchesne 6b2b8ab42a fix!: le registre d-ouverture n-a plus qu-un seul sens, et il cesse de mentir
Trois constats, un seul defaut sous trois visages : un etat enregistre comme vrai
qui ne l'est pas, sans moyen de se corriger. Deux des trois vivaient dans
`open-repo.ts`. Cinq tours sont alles dans l'appelant ; c'est le teneur de
registre qui mentait.

CE QUE LE REGISTRE VEUT DIRE, ecrit dans l'en-tete. `held` est le SEUL registre
d'ouverture : cette session detient un abonnement vivant a ce document. Il y avait
a cote un `opened` voulant dire « on a appele `doc_subscribe` une fois et on a
cesse d'attendre », et ce sens plus lache mentait de trois facons : il survivait a
un abonnement REFUSE par le courtier (donc la reprise que `subscribe.ts` offre
etait bloquee a jamais), il survivait a une reinitialisation (donc une nouvelle
session court-circuitait sur l'enregistrement de la precedente), et il laissait un
document annoncer une barriere jamais franchie. `opened` est supprime.

Les trois consequences, desormais vraies par construction :
- un abonnement qui a ECHOUE n'enregistre rien - `onSetupFailed`, le canal que
  `subscribe.ts` offre exactement pour cela, est enfin branche - donc l'appelant
  suivant s'abonne a nouveau ;
- une reinitialisation efface l'enregistrement ET DECLASSE l'etat de synchro :
  une barriere franchie dans la session qui vient de disparaitre n'en est pas une
  que celle-ci a franchie ;
- `"unknown"` ne veut plus dire qu'une chose : aucune barriere n'etait attendue ici
  (jamais demande, ou un `ng` sans `doc_subscribe`). Jamais « on a oublie ».

Cote appelant, un seul changement, et c'est le troisieme constat : quand un
document nomme n'a pas repondu, `publish` PUBLIE « la question reste ouverte » au
lieu de sortir en laissant en place l'instantane precedent. Sortir laissait un
refus DEJA LEVE a l'affichage : l'application lisait « vous ne detenez pas la
capacite de ce document » a propos d'un document qu'elle detient desormais.

Deux tests portaient l'ancien sens et sont corriges avec leur raison : celui du
`ng` sans `doc_subscribe` (le compteur d'ids du faux courtier redemarrait a chaque
test, donc deux tests aliasaient leurs depots - la lecon de `wallet-fake` a
l'echelle du fichier), et celui de la lecture froide, ou « timed-out » est
desormais ce que dit « cette page ne l'a pas confirme ».
2026-09-24 11:34:10 +02:00
Sylvain Duchesne 4af0be433d fix!: joindre un document n-est pas en avoir des nouvelles, et une reinit invalide ce qu-elle abandonne
Deux defauts, aucun des deux sur la reprise - elle a passe cette revue intacte et
n'a pas ete rouverte.

1. La barriere verifiait qu'un document nomme peut etre JOINT, jamais qu'il a
   REPONDU. Elle attendait `ensureRepoOpen` puis demandait `assertMayReach`, qui
   est la question de la capacite, et ne consultait pas l'etat de synchro que
   l'ouverture venait de produire. Un document dont l'abonnement a reussi mais dont
   le premier `State` n'est jamais venu publiait donc `isSuccess` avec `data: []` :
   le defaut d'origine, dans le cas precis pour lequel la barriere a ete ajoutee.

   La verification rejoint le reste de la decision, dans `publish`, donc rejouee a
   chaque publication au lieu d'etre memorisee. Les trois valeurs, enumerees avant
   d'ecrire la condition : `"synced"` fait autorite - zero ligne VEUT DIRE zero
   objet, la ligne tenue depuis cinq revues -, `"timed-out"` et `"syncing"` non, et
   `"unknown"` PASSE deliberement, parce qu'il veut dire « pas de semantique de
   `State` sur ce `ng` » et que le traiter autrement est exactement la regression
   du cas inerte livree aux tours 1 et 3.

   `isPending` plutot que `isError`, des deux que le contrat autorise : rien n'a
   echoue, il manque l'autorite de dire que le document est vide.

   Le test (t) EPINGLAIT le defaut - il attendait `isSuccess` apres trois
   barrieres expirees. Corrige. Ce n'est pas un test affaibli : l'ancienne
   assertion etait fausse.

   Et un trou dans ma propre suite, trouve par mutation : inverser la condition
   pour refuser `"unknown"` passait tous les tests, faute d'un `ng` SANS
   `doc_subscribe` nulle part. (cc) le couvre.

2. Regression de mon correctif du tour 4. Terminer une ouverture abandonnee
   faisait tourner sa continuation juste apres que la reinit avait vide les
   tables, donc elle ecrivait une entree `opened` perimee dans le registre neuf -
   et toute ouverture ulterieure de ce nuri court-circuitait dessus. Le depot
   n'etait jamais abonne dans la nouvelle session. Une epoque de registre corrige
   cela : une reinit l'incremente, une continuation dont l'epoque ne correspond
   plus n'ecrit rien. Une reinit INVALIDE ce qu'elle abandonne, en plus de le
   terminer.

`getSyncState` reste interne, et la doctrine cesse de l'offrir : elle pointait sur
un symbole qu'aucun appelant ne peut importer.
2026-09-24 11:03:07 +02:00
Sylvain Duchesne 8c890411bb fix!: la reprise cesse de dependre d-un front, et une ouverture abandonnee se termine
Quatrieme tour. Ce n'est pas un drapeau de plus : c'est la forme qui change.

Les tours 2 et 3 construisaient tous deux la reprise ainsi - noter qu'on a
refuse, puis reagir a un evenement ulterieur - et c'est la note qui etait
fautive les deux fois : une fois conditionnee a quelque chose qui ne pouvait pas
devenir vrai, une fois ecrite apres la fenetre ou arrive l'evenement decisif. Un
front manque n'est pas rattrapable par construction. Donc :

- LA DECISION EST UN NIVEAU. Les documents nommes sont reinterroges a chaque
  publication, jamais reportes depuis la barriere. Une capacite qui arrive change
  `isError` en document par le canal qui existait deja, et rien n'etant memorise,
  il n'y a aucun evenement a manquer. C'est aussi ce qui rend sans objet la
  question du porteur : une requete ouverte pendant qu'un autre etait connecte ne
  peut pas revenir a cet abonne en succes auquel il n'a pas droit.
- UNE TENTATIVE QUI A COURU CONTRE UN CHANGEMENT S'EN APERCOIT au lieu
  d'attendre. `open()` echantillonne `CapRegistry.version()` avant la barriere et
  compare apres son echec ; un ecart signifie qu'elle a decide sur un terrain
  perime, et elle retente d'elle-meme. Cela termine : une tentative ne se repete
  que parce que le trousseau a bouge, les mouvements sont finis, et ceux qu'une
  tentative provoque elle-meme sont les capacites qu'elle depose - au plus une par
  document nomme.

La comparaison de porteur disparait : `holderKey()` vaut "" tant que personne
n'est connecte, donc un refus pris avant que l'identite se fixe - la ou les
composants se montent - ne correspondait plus jamais ensuite.

Un instantane d'echec inchange n'est plus republie : la surface documente sa
stabilite, et huit arrivees sans rapport produisaient huit objets porteurs de huit
`Error` distinctes pour un etat identique.

Enfin, un defaut a moi, trouve en mesurant : le changement rendait la suite
intermittente. `resetOpenedRepos()` abandonnait les ouvertures en vol sans les
terminer, donc un appelant attendait les 8s du repli sur un abonnement demonte,
puis repartait avec son etat de synchro efface - et « unknown » est la lecture
LAXE, celle qui accepte qu'une lecture a zero ligne soit une absence averee. Une
absence-qui-cache-un-echec latente, de la famille meme de cette tache. Une
reinitialisation termine desormais ce qu'elle abandonne, en « timed-out ».

Deux fixtures qui se couraient apres sont corrigees, raison ecrite a cote.
2026-09-21 17:10:20 +02:00
Sylvain Duchesne f875047099 fix!: la garde circulaire disparait, et une nouvelle tentative se fait au nom de qui a ete refuse
Troisieme tour de revue adversariale, trois constats, tous reproduits avant
correction.

1. La garde etait CIRCULAIRE, et c'etait structurel. `namedDocumentsReachable`
   demandait si la capacite est DETENUE. Pour un document en store public, le seul
   canal qui puisse jamais la deposer est `fetchReadCap` - l'appel meme que la
   garde retenait. Elle attendait une reponse que seule l'action qu'elle bloquait
   pouvait produire, et toute cette classe restait irrecuperable.

   La bonne question n'est pas une meilleure garde, c'est de RETENTER. Les deux
   facons d'etre refuse se reparent par des choses opposees, et le memo les
   distingue deja : un echec n'est deliberement pas memoise, donc une nouvelle
   tentative redemande vraiment au reseau - c'est la reparation de cette classe,
   pas un cout a eviter ; une absence est memoise, donc une nouvelle tentative ne
   demande a personne et coute un instantane, jusqu'au jour ou la capacite arrive
   d'ailleurs, seule chose qui repare celle-la. La garde est supprimee : le module
   est plus petit qu'au tour precedent.

2. La reouverture faisait un ACCES sous la mauvaise identite. Republier REFILTRE
   une requete existante ; rouvrir EMET un abonnement et une requete au store
   public pour un document que cet abonne a nomme. C'est un acte neuf, pas un
   ancien qui ressort, et l'etat final etait `isSuccess` avec `data: []` pour un
   document illisible - le mensonge que cette tache supprime, par la porte du
   correctif. `refusedFor` retient QUI a ete refuse ; on ne retente que tant que
   c'est encore lui le connecte.

   Etabli plutot qu'affirme : une application ne peut pas changer d'identite en
   cours de session. `ensureIdentity` est le seul appel publie qui en etablit une,
   et `resolveIdentity` commence par `getCurrentUser()`. Corrige quand meme.

3. `Promise.all` court-circuite a la premiere rejection ARRIVEE, et un fetch qui
   rejette n'attend pas la barriere de synchronisation : il double un refus portant
   sur un document nomme avant lui. Le comportement est corrige plutot que le
   commentaire - `allSettled` puis parcours dans l'ordre du scope.

Et le quatrieme controle e2e est retire, pas repare : il passait avec toute la
machinerie de reprise supprimee. Un controle qui ne peut echouer pour aucune
regression de ce qu'il garde se lit comme de la couverture et vaut moins que rien.
2026-09-21 16:28:24 +02:00
Sylvain Duchesne f36ed5c164 fix!: un scope nomme refuse se rouvre quand la cle arrive, et la barriere cesse d-etre serie
Revue adversariale : la barriere livree au tour precedent rendait le refus
TERMINAL. Le signal du trousseau s'ecrit `if (query !== null) publish(...)`, parce
qu'avant la barriere il n'y avait qu'une seule raison de n'avoir pas de requete -
rien n'avait encore ete tente - et qu'une cle qui arrive ne pouvait changer que ce
qu'une requete OUVERTE avait le droit de montrer. La barriere en a ajoute une
seconde : elle abandonne AVANT que la requete existe, donc le seul canal qui fait
converger une cle tardive passait a cote de l'etat qui en avait le plus besoin.
Une page qui s'abonne pendant que son trousseau se restaure, ou un document
partage sous les yeux de son destinataire, publiait `isError` et y restait. Ce
n'est pas une surface plus stricte, c'est une pire : echanger un vide mensonger
contre une erreur bloquee ne repare rien, et cela casse une garantie que ce paquet
publie.

Le signal rouvre donc un scope refuse, et seulement quand le refus est leve
(`namedDocumentsReachable`). Le garde-fou porte autant que la reouverture :
rouvrir a chaque changement de trousseau rejouerait la barriere pour chaque cle
qu'une connexion depose, redemanderait au reseau tout document dont le
telechargement avait ECHOUE - un echec n'est deliberement pas memoise - et
republierait la meme erreur a chaque abonne. La variante indiscriminee casse deux
tests, dont celui de demontage qui preexistait.

La barriere passe en parallele. Elle etait serie, donc N documents qui ne poussent
jamais de `State` coutaient N replis bornes, 8s chacun en production. Les refus
restent lus dans l'ordre du scope apres coup.

Et ce qui a laisse passer les deux : l'e2e ne parcourait pas ce chemin. Les deux
journeys `useShapeQuery` lisent `{graphs: "did:ng:i"}`, donc 43/43 n'a jamais rien
prouve sur la barriere. Quatre controles ajoutes sur un scope NOMME, contre le
vrai broker : la proprietaire lit son document, un porteur de rien recoit
`isError` et jamais un succes vide, un document PUBLIC s'ouvre pour lui depuis la
seule reference via la requete de la barriere, et une cle qui tombe pour un AUTRE
document laisse le scope refuse en place.
2026-09-21 15:57:56 +02:00
Sylvain Duchesne b0eec7b4dc docs: le contrat passe en 1.0.0-dev.9, et ce qu'une lecture echouee repond est consigne
Majeur : trois appels existants se resserrent. `useShapeQuery` publie `isError`
pour un document NOMME qu'il ne peut pas lire, la ou il rendait `isSuccess` avec
`data: []` - la seule image que cette surface reserve a « ce scope ne contient
rien ». `inbox.postToDocument`, `readForDocument` et `readSyncedForDocument`
rejettent la ou elles annoncaient « pas d'inbox ». Rien de ce qui etait deja une
reponse ne bouge : un document vide, `graphs: []`, un document sans inbox, un
document public qui n'expose aucune cle.

Deux non-garanties corrigees parce que le changement les rend fausses : « aucune
des deux lectures par forme ne va chercher la cle d'un store public » ne vaut
plus que pour `useShape` et pour `did:ng:i`, et le pending eternel d'un demarrage
refuse se referme sur le cas nomme.

Nouveau `knowledge_what-a-failed-read-answers-upstream` : ce que la cible repond
pour un document injoignable, non synchronise ou vide, et les deux collisions que
l'appelant ne peut pas demeler - « n'existe pas » contre « pas connu de moi »
d'un cote, « non synchronise » contre « vraiment vide » de l'autre. C'est le fait
durable qui justifie la direction prise, et il vaut quel que soit le remede.

`docs/internal-contract.md`, section open-repo seulement : numeros de ligne,
`ensurePhysicalRepoOpen` qui n'a jamais ete un export de ce module,
`ensureReposOpen` supprime, `openRepoUnguarded` absent, et le second parametre de
`ensureRepoOpen`. Le reste du document porte une derive du meme age, laissee.
2026-09-21 15:09:50 +02:00
Sylvain Duchesne 0c21adebf2 feat!: retirer readUnion de la surface publiee, et lire un document par requete ancree
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.
2026-09-21 14:08:05 +02:00
Sylvain Duchesne d0ea043a5b feat!: la famille ORM est publiee enveloppee, et le ng publie cesse de rendre des promesses 2026-09-21 12:38:17 +02:00
Sylvain Duchesne 77e78ec81d feat!: useShapeQuery lit par la requete de forme du SDK 2026-09-21 11:41:31 +02:00
Sylvain Duchesne d0ca5c5d41 feat!: useShapeQuery prend le Scope du SDK, et un store se nomme par sa reference 2026-09-16 16:47:09 +02:00
Sylvain Duchesne f6a48a4507 fix: un dépôt est délivré une fois et retiré, comme en amont 2026-08-26 14:11:56 +02:00
Sylvain Duchesne abfb619c76 docs: ce qu'un IRI dit en amont, et pourquoi l'échafaudage ne partage pas le namespace de la donnée 2026-08-21 22:28:09 +02:00
Sylvain Duchesne fafac38907 docs: dire l'observation continue, sa limite, et ce qu'ensureIdentity ne promet pas 2026-08-17 14:47:40 +02:00
Sylvain Duchesne 6eaff0b985 docs: une seule copie du polyfill par application, sinon deux identités courantes 2026-08-17 11:48:25 +02:00
Sylvain Duchesne 72bb72c0b7 docs: useShape ne démarre pas contre un broker antérieur au renommage 2026-08-17 11:05:38 +02:00
Sylvain Duchesne bf1e16e1f3 docs: un tag de dépôt ne dit pas quel paquet il a figé 2026-08-17 10:53:05 +02:00
Sylvain Duchesne 1957a8fe0c docs: des contrats à deux voix, et une politique de version qui dit la vérité
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.
2026-08-17 10:10:12 +02:00