Deux feuilles affirmaient que la richesse de la config ORM est perdue au
niveau 1 - "no caller can reach any of it. The richness is real and
unreachable". La preuve invoquee etait le `json!({})` de
`Verifier::start_orm` a 213338f6, et elle ne porte pas cet enonce.
Ce qui est verifie a 213338f6 : `OrmConfig` declare where_, order_by,
select, page_size et max_active_pages ; `from_json` les analyse tous les
cinq et refuse un pageSize sans orderBy ; schema_shape_to_sparql et le
pagineur les consomment. La machinerie est implementee. Il manque un
seul saut, a l'ENTREE : start_orm ne prend aucun argument de config, et
son TODO voisin nomme le maillon absent, "use json in libwasm context".
Or ce maillon existe dans le build : @ng-org/web@0.1.2-alpha.14 publie
orm_start_graph avec SIX parametres, config en position 4, quand l'arbre
cite en declare CINQ, et @ng-org/orm@0.1.2-alpha.21 l'appelle avec six
en passant tout son objet d'options. Lus dans les artefacts publies.
Donc la config quitte le niveau 3 et est acceptee au niveau 2 aux
versions installees. Ce que le moteur derriere ce binding en fait n'est
etabli par rien d'atteignable, et c'est dit ainsi.
Une citation nomme un chemin et un symbole, jamais une version. Or le
checkout contre lequel toutes les citations `engine/` et `sdk/` se
resolvent declare des versions inferieures a celles que ce depot
installe, donc une citation prouve "c'etait vrai a ces versions-la" la
ou un lecteur lit "c'est vrai de ce qu'on construit".
La version est une propriete du CHECKOUT, pas d'une citation : aucune
des annotations n'est touchee. Elle est enregistree une seule fois, dans
l'`ANCHOR` de provenance.test.ts, a cote du resolveur qui s'en sert.
Ce que le mecanisme verifie maintenant, aux DEUX bouts de l'ecart, parce
qu'un enonce d'ecart peut perimer par l'un ou l'autre et a deja perime
par le bout eloigne : les versions que le checkout declare (a vide quand
le checkout est absent, comme la verification de resolution), celles que
les manifestes de l'espace de travail epinglent, et celle qui est
reellement installee sous packages/polyfill.
Pas un nouveau `kind` : les kinds classent la preuve d'UN symbole,
l'anciennete de toute la preuve appartient a l'arbre.
Digest complet dans le fichier de tache : les quatre ranges "*" du
polyfill mesurees contre le registre (orm etait le cas le pire, "*"
n'y admettait aucune version), le plancher choisi par paquet a partir
du devDependencies actuel plutot que copie de ng-e2e-helpers, le
verdict peerDependenciesMeta confirme par lecture du code (imports
type-only uniquement), et l'installation fraiche verifiee a une seule
copie de chaque @ng-org.
examples/notebook typecheckait correctement en invocation directe mais
n'avait aucun script pour le lancer, donc rien dans le depot ne le
verifiait automatiquement. Ajoute "typecheck": "pnpm exec tsc --noEmit -p
tsconfig.json", sur le meme modele que packages/ng-e2e-helpers.
packages/polyfill/package.json declarait "*" sur les quatre @ng-org/*, ce
qui en semver n'admet aucune prerelease. Mesure sur le registre : "*"
n'admettait que le 0.1.2 bru (2025) pour web/shex-orm/alien-deepsignals et
ZERO version pour orm (qui n'a jamais publie de 0.1.2 stable). Les quatre
ranges deviennent ">=<pin actuel> <0.1.2>", le plancher etant la version
dont ce paquet est effectivement construit et teste (devDependencies) et
le plafond excluant le 0.1.2 perime, sur le modele mesure par T01.l pour
ng-e2e-helpers.
Le Result disait « pas un defaut aujourd'hui ». La reproduction le
falsifie : la fenetre est d'un saut de micro-tache, balayee de 0 a 6, et
le test echoue sans le correctif. Le suivi devient une ronde close, avec
l'ordonnancement discriminant consigne et la severite - diagnostique,
aucune lecture ne gagne d'autorite - dite explicitement.
Dernier chemin pouvant remettre une barriere deja oubliee. Le rejeu
lisait `lastState` a la souscription et le livrait une micro-tache plus
tard ; l'etablissement peut echouer dans cet intervalle, et
`reportSetupFailure` desavoue son `State` avec lui.
La fenetre est d'exactement UN saut de micro-tache, mesuree et non
raisonnee : a zero saut l'effacement n'a pas encore eu lieu, a deux ou
plus `lastState` est deja nul donc aucun rejeu n'est mis en file et
l'arrivant ouvre une tentative neuve. `isCurrentAttempt` ne couvre pas
ce cas : cet echec n'incremente pas `attempt`, donc l'entree et la
tentative sont toutes deux encore les courantes.
DIAGNOSTIC, et cadre comme tel : `getSyncState` lit l'enregistrement et
non la poussee, donc il repond toujours `"timed-out"` et aucune lecture
ne gagne d'autorite. Ce que la livraison perimee cassait est l'attente
bootstrap de `open-repo`, qui franchissait sa barriere dessus et
journalisait `BARRIER <nuri> synced` pour une barriere qui n'existait
plus - or cette ligne de trace est celle qui distingue une absence reelle
d'une lecture non synchronisee.
J'avais ecrit ce remede puis l'avais RETIRE, faute de test qui
discrimine. L'ordonnancement ci-dessus est ce qui manquait ; le test
echoue sans le correctif et passe avec.
Le Result disait que la troisieme passe n'avait pas rapporte et que
56072df n'avait donc pas de revue tierce. C'est faux depuis qu'elle a
rendu : elle refute 226db18 sur exactement le point trouve par
enumeration, reproduit independamment, confirme que 56072df le corrige,
et ne trouve rien d'autre. Sa porte et son balayage anti-triche sont
consignes.
Son unique observation - le rejeu capture lastState au lieu de le
relire - a ete sondee : la remise a lieu tant que la barriere TIENT, et
aucun ordonnancement ou la capture differe d'une relecture n'a pu etre
construit. La relecture a ete ecrite puis RETIREE, faute de test qui
discrimine ; elle est consignee en suivi, pas en correctif.
Le compte des enregistrements, l'invariant, les chemins de demontage, les
deux echecs prouves devenus des tests, trois rondes adverses et ce qu'elles
ont coute, la porte verbatim et les deux non-garanties retirees.
Trouve en enumerant moi-meme les ecritures de `establishing` et de
`lastState` plutot qu'en croyant l'argument du commit precedent, qui
affirmait que le garde retire etait inatteignable. Il ne l'etait pas.
`isCurrentAttempt` dit que cette entree et cet appel sont encore les
bons ; il ne dit rien sur le fait que l'appel ait REUSSI. Le courtier
expedie avant de repondre, donc un `State` peut arriver apres que
l'appel a deja ete rapporte en echec - et ce module tient lui-meme pour
atteignable qu'une poussee survive a son demontage, c'est la raison
d'etre du garde sur ce callback. Mesure : la poussee tardive recreait la
barriere morte que `reportSetupFailure` venait d'oublier, sans canal
derriere, et `getSyncState` y repondait `"synced"` - donc autorite.
`lastState !== null` implique desormais qu'une tentative tient, a
l'ecriture comme a l'effacement, donc les deux ne peuvent plus diverger
et aucun lecteur n'a a le reimposer. La poussee reste diffusee aux
auditeurs : seule la BARRIERE est une affirmation sur le document.
Seconde revue adverse, sur le correctif precedent - et elle a raison :
celui-ci MASQUAIT la barriere perimee au lieu de l'oublier.
`reportSetupFailure` effacait `establishing` et laissait `lastState`. Le
premier correctif avait ajoute « et un canal tient » du cote du LECTEUR,
ce qui ne cachait la barriere morte que tant que rien ne tenait. La
souscription suivante sur le meme fan-out remettait `establishing` a
vrai, le masque tombait - et le fan-out rejouait ce `State` au nouvel
arrivant. Mesure : l'ouverture bootstrap franchissait sa barriere en 2 ms
sur un `State` d'un appel qui avait ECHOUE, journalisait
`BARRIER ... synced`, et `getSyncState` repondait `"synced"` - donc
autorite, donc `noRowsMeansAbsent` prenait une lecture a zero ligne pour
une absence definitive. Le depot detruit que le commit precedent
pretendait empecher.
Corrige a la source : une tentative qui echoue oublie sa barriere, comme
le font deja `releaseFanOut` et `resubscribeDocs`. Le garde du lecteur
est RETIRE avec, et pas garde en ceinture-bretelles : il etait devenu
inatteignable, et c'est lui qui avait cache la faute. Apres : la barriere
est oubliee, l'ouverture attend la sienne (202 ms), `"timed-out"`.
Et le test que j'avais ecrit affirmait le defaut - `barrierCrossed`
vrai - au lieu de l'etat correct. Corrige, plus un second test qui
conduit le rejeu de bout en bout.
Le test mesurait un ecart de 35 ms (repli de 60 ms, borne a 25). Ce qu'il
constate est la difference entre « a attendu la barriere » et « n'a pas
attendu du tout » ; 200 ms de repli et une borne a 60 laissent 140 ms
entre les deux, ce qui tient sur une machine chargee. La mutation qui
doit le tuer le tue toujours.
Chacun reproduit avant d'etre corrige, chacun epingle par un test qui
echoue si la correction est retiree.
1. Une barriere pouvait survivre a la tentative qui l'avait produite.
`getSyncState` repondait `"synced"` sur le seul `barrierCrossed`, alors
que son propre commentaire disait « sur le canal qui tient MAINTENANT » :
`reportSetupFailure` efface `establishing` et laisse `lastState`, donc un
`doc_subscribe` qui poussait un `State` puis echouait laissait une
autorite - et `noRowsMeansAbsent` prend une lecture a zero ligne pour une
absence definitive. Le registre precedent repondait `"timed-out"` la ;
strict est la lecture de cette famille, son cout est la latence, jamais
la perte.
2. `release` ne repondait pas « est-ce que j'ecoute encore ».
`resetDocSubscriptions()` largue les auditeurs et supprime l'entree sans
rien dire, donc le handle continuait de paraitre vivant ; si autre chose
souscrivait d'abord au meme document, le court-circuit sautait l'ouverture
due et le document restait tenu par un auditeur libre de partir. Le
fan-out numerote desormais ses incarnations, et l'ouverture compare.
3. Une tentative sans reponse TIENT quand meme. Elle ne comptait pas, si
bien que chaque lecture d'un document dont le `doc_subscribe` ne repond
jamais repayait le repli borne - 8 s en production, sur le chemin de la
lecture ancree. Un second `doc_subscribe` est de toute facon impossible
tant qu'un est en vol, donc la tentative fraiche n'achetait rien.
Les trois questions sont maintenant une fonction nommee, `stillStanding`,
et chacune est posee a qui detient la reponse.
L'enonce disait « seulement l'auditeur pose » ; l'enregistrement porte
aussi l'attente bornee. Les deux sont du ressort de la demande, pas du
fait - c'est ce que la phrase doit dire.
Le JSDoc de `resetOpenedRepos` parlait encore de demonter "les
souscriptions bootstrap detenues", et celui de `getSyncState` decrivait
ses valeurs comme stockees. Les deux disent maintenant ce que le code
fait : liberer les auditeurs et desavouer les documents, et deriver.
Le commentaire de `namedDocumentsArrived` decrivait `open-repo` comme
gardant trois valeurs, et disait que l'attente "resout sur le State ou
sur le repli, et chacun pose sa propre valeur" - plus vrai depuis que
rien ne les stocke.
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.
Deux blocs disaient « la souscription est maintenue vivante pour la
session », l'un accroche a onSetupFailed et l'autre a l'appel de
souscription - exactement le defaut de commentaire orphelin que cette
tache devait corriger ailleurs.
Le commentaire affirmait qu'aucune des deux valeurs ne permet de prendre une
lecture a zero ligne pour une absence definitive. `unknown` le permet, et
deliberement : `index-deposit.noRowsMeansAbsent` l'accepte, parce que la ou il
n'y a pas de semantique `State` il n'y a pas de barriere a avoir manquee. Les
deux valeurs ne sont pas interchangeables, et laquelle est juste depend de la
page - ce que l'assertion epingle dans les deux mondes est la meme chose : pas
`"synced"`.
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.
Le critere d-acceptation a ete execute, pas raisonne : lien reel contre le
checkout, resolution confirmee par readlink, defaire par `pnpm remove`. Le
polyfill lui-meme n-est jamais duplique par le lien ; le SDK en dessous l-est
des que l-application importe `@ng-org/web` elle aussi - 2 copies mesurees.
Les deux textes expliquaient que `bun install` ne va pas chercher le pair
optionnel. Mesure : pnpm ne le va pas chercher non plus pour un consommateur
qui ne le nomme pas (un paquet qui ne declare que le polyfill recoit zero
@ng-org, sans avertissement) - mais ce depot-ci l-obtient quand meme, parce
que `packages/polyfill` le declare aussi en devDependency. Un `pnpm install`
depuis un `node_modules` vide suffit donc a faire construire le bundle du
runner, et le message qui demandait d-installer l-ORM a la main etait devenu
un faux conseil.
Suite e2e du polyfill rejouee sur 1.63.0 : 46 passed, 0 failed, 46 total,
batch 3.3 min. Le binaire Chromium attendu (chromium-1243) etait deja en
cache. A savoir : 1.63.0 remonte son `engines.node` de >=18 a >=20, et ne
declare plus `fsevents` en optionalDependency - d-ou sa disparition du
lockfile.
Rien ne s-installe avec bun dans ce depot et `packageManager` declare
pnpm@10.26.0, mais `bunx tsc` obligeait encore un contributeur a avoir bun
sur sa machine pour typechecker. Verifie en retirant bun du PATH : les deux
`typecheck` sortent en 0 avec bun injoignable.
`bun test` reste : c-est le lanceur de tests, pas un installeur.
Le `>=0.1.2-alpha.13` declare par ng-e2e-helpers admettait quatre versions
publiees, dont le `0.1.2` nu du 2025-10-19 que semver classe au-dessus de
chaque `0.1.2-alpha.N` : `semver.maxSatisfying` rendait ce `0.1.2`, soit
un retour d-environ un an. `>=0.1.2-alpha.13 <0.1.2` n-admet plus que les
trois alphas, et pnpm y resout `0.1.2-alpha.15`.
Ce qu'elle livre a tenu six revues adversariales independantes : les quatre sites
d'appel, la suppression de `fetchReadCaps`, la feuille de connaissance, les gardes
« vide = succes », la couverture e2e des scopes nommes - jouee en direct par deux
relecteurs -, et la moitie « joignabilite » de la barriere.
Ce qu'elle ne livre pas est ecrit comme tel plutot que promis : la moitie
« arrivee » vaut dans une session ordinaire, pas au-dela. Le defaut de registre
qui l'en empeche part dans T01.m.
Porte de sortie : 449 tests verts six fois de suite (3.18-3.34s), les deux
typecheck a 0, lint 0 ERROR, e2e 46/46 rejouee sur l'etat livre.
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.
Les trois constats comme un seul defaut, l'invariant du registre et ce qui
dependait du sens plus lache, verifie plutot que suppose. Et la lecon consignee
sans l-expliquer : la garantie publiee au tour precedent n'etait pas tenue, c'est
la deuxieme fois que cette tache livre du texte de contrat avant le comportement,
donc desormais la garantie et le test qui l'epingle arrivent dans le meme commit.
Ma reponse a l'arret dur, donnee sans attendre la question : ce tour etait
contenu - un invariant, un module, quatre lecteurs enumeres - et l'appelant en
ressort plus petit, pas plus grand. Si une passe retrouve cette famille, je
prendrais la scission moi-meme.
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 ».
Les deux constats de la revue, l'enumeration des trois valeurs d'etat de synchro
ecrite avant la condition, le trou de ma propre suite trouve par mutation (refuser
« unknown » passait tous les tests), et la phrase de contrat que je venais
d'ecrire et qui etait fausse jusqu'a ce que `onState` enregistre un `State`
tardif. Chiffres corriges, et la derive des tours precedents avec eux.
En relisant ce que je venais d'ecrire dans le contrat - « si le document se
synchronise plus tard et apporte des donnees, la lecture repond d'elle-meme » -
j'ai constate que c'etait faux. `onState` sortait aussitot des lors que le repli
borne avait deja termine l'ouverture, donc un `State` tardif etait jete :
`"timed-out"` restait indefiniment sur un depot desormais synchronise, et le
lecteur qui consulte cet etat ne pouvait jamais l'apprendre.
Le repli veut dire « cette ouverture a cesse d'attendre », jamais « la barriere ne
sera pas franchie » : l'abonnement n'est pas ferme, donc le `State` arrive encore.
`onState` enregistre desormais `"synced"` avant le controle. `"timed-out"` ->
`"synced"` est la seule transition ajoutee, et elle va de moins d'autorite vers
plus ; les deux lecteurs de cette valeur la veulent ainsi.
(dd) l'epingle : elle tombe si l'enregistrement repasse sous le controle.
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.
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.
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.
Le controle « une cle qui tombe pour un AUTRE document laisse le scope refuse »
mesure l'ISSUE, pas le ciblage : il ne separe pas « aucune reouverture n'a ete
tentee » de « une a ete tentee et refusee a nouveau », les deux finissant au meme
endroit. Le ciblage se mesure au compte de notifications, dans la suite unitaire.
Nommer ce qu'un controle vert prouve est toute la lecon du tour qui a livre cette
barriere sans aucune couverture de scope nomme.
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.
L'enumeration a ete ecrite avant le code, comme demande : les quatre sites
d'appel, ce que chacun fait aujourd'hui quand la capacite ne peut pas etre
obtenue, le cas inerte, et la liste de ce qui doit rester un succes. Le resultat
consigne le choix, les quatre options ecartees avec leur raison, ce qui a casse
et ce que ces tests affirmaient vraiment, et les deux bug_ qui ne se ferment pas.
(p) `graphs: ""` est l'autre orthographe du dataset entier, jamais un nom de
document : le filtre reste seul a decider. C'est une garde, pas un test
discriminant - `isNuri` ecarte deja la chaine vide -, et elle epingle le
comportement plutot que le chemin.
(q) se desabonner pendant que la barriere est en train d'etre franchie ne doit
laisser aucune requete ouverte. La barriere rend l'ouverture asynchrone, donc il
existe desormais une fenetre ou l'observable a des abonnes, pas de requete, et une
ouverture en chemin ; verifie en retirant le controle de generation, ou la
requete est creee apres le dernier auditeur et plus rien ne la ferme.
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.
`fetchReadCap` rendait `false` pour trois situations sans rapport : l'emulation
inerte, le document qui n'expose aucune capacite, et le reseau qu'on n'a pas pu
interroger. Les trois appelants vivants lisaient ce `false` comme « pas de
capacite » et transformaient une panne en affirmation sur le document :
`ensureRepoOpen` renoncait en silence et laissait la lecture suivante accuser le
lecteur de ne rien detenir, et `documentInboxAddress` repondait « ce document n'a
pas d'inbox », ce qu'une application lit comme zero depot.
Le telechargement rejette desormais jusqu'a l'appelant, avec le document nomme et
la cause d'origine conservee. `false` ne veut plus dire qu'une chose : le document
n'expose rien, et c'est une reponse. Le memo garde la meme asymetrie qu'avant.
`documentInboxAddress` propage aussi l'echec de sa propre lecture d'adresse : le
`catch` qui rendait `undefined` se justifiait par un refus que `mustNotAttempt` a
deja ecarte une ligne plus haut, donc il n'avalait que des pannes.
`useShapeQuery` franchit la barriere sur chaque document NOMME par `scope.graphs`
avant d'ouvrir la requete : capacite demandee au store public, refus a voix haute
si elle n'est pas detenue, ouverture et synchronisation. Un document nomme
qu'on ne peut pas lire donnait `isSuccess` avec `data: []` apres que le filtre de
capacites l'eut vide - la seule reponse que cette surface existe pour reserver a
« ce scope ne contient rien ». Restent intacts et succes : `did:ng:i`, `graphs:
[]`, un document joignable et vide, et tout le cas inerte.
`fetchReadCaps` supprime : aucun appelant, et une politique de tolerance par lot
sans proprietaire au-dessus d'un appel qui rejette desormais.
The example's real bug was a missing satisfies clause widening
NoteShape's valType, not the pnpm-workspace.yaml gap the lead pointed
at - that gap was real too and got fixed, but for a different reason
(examples/notebook shared node_modules/.bun with the store this task
also deletes).
node_modules/.bun held stale @ng-org/web, @ng-org/shex-orm and
@ng-org/alien-deepsignals copies beside the pnpm-managed current ones
(T01.h left it in place, latent). Deleted the directory; every symlink
in the tree already resolved through node_modules/.pnpm, so nothing
pointed at it.
The likely trigger for a future accidental bun install was a bun.lock
committed at the repo root (the gitignore only listed the older
bun.lockb, so this text-format lockfile slipped through). Removed the
tracked bun.lock and added both bun.lock and bun.lockb to .gitignore,
and declared packageManager: pnpm@10.26.0 in package.json so tooling
(and anyone reading it) has one unambiguous answer for which installer
this tree uses. No new tooling built, per the task's instruction.
pnpm-workspace.yaml listed only packages/*, so pnpm never installed
examples/notebook's dependencies even though root package.json's
workspaces field named examples/* too; the example ran on a stray
node_modules a manual bun install had left behind (symlinks into
node_modules/.bun, including a dangling @ng-eventually/sdk link to a
package that no longer exists). Added examples/* to pnpm-workspace.yaml
and reinstalled; examples/notebook now resolves @ng-org/orm and
@ng-org/web to the exact same pnpm store instances packages/polyfill
uses (verified via the symlinks and node_modules/.pnpm listing) - no
package ended up duplicated.
That alone did not fix the example's TS2345: NoteShape's object
literal had its dataTypes[].valType widened from the "iri"/"string"
literals to plain string (no satisfies clause), which the existing
test suite already guards against elsewhere (satisfies
ShapeType<BaseType>). Applied the same pattern here, importing the
already re-exported ShapeType/BaseType from @ng-eventually/polyfill -
no new dependency needed, no compiler option touched.