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.
This commit is contained in:
@@ -989,6 +989,73 @@ const identity = new IdentityStore(
|
||||
if (rec) rec.unsub();
|
||||
setCurrentUser(null);
|
||||
},
|
||||
/**
|
||||
* NAMED-SCOPE seed. Under a fresh identity, create TWO entity documents carrying the
|
||||
* shape's type - one PROTECTED, one PUBLIC - and seed each with a distinguishable
|
||||
* title. Returns the owner and both references, which is all any of the three named
|
||||
* subscriptions below is given.
|
||||
*
|
||||
* Why this exists at all: until `1.0.0-dev.9` every `useShapeQuery` journey in this
|
||||
* suite read `{ graphs: "did:ng:i" }`, so the scope axis that NAMES a document was
|
||||
* measured nowhere against the real broker - and the barrier that names introduced
|
||||
* shipped with no live coverage. A suite that is green on a path it never walks is
|
||||
* worse than one that is red.
|
||||
*/
|
||||
async useShapeQueryNamedSeed(cls: string) {
|
||||
registryInternals.resetRegistryCache();
|
||||
const owner = "@wsn-" + Date.now();
|
||||
setCurrentUser(owner);
|
||||
const s = await sessionReady;
|
||||
const prot = await storeRegistry.createEntityDoc(await storeRegistry.protectedStore());
|
||||
await docs.sparqlUpdate(
|
||||
s.session_id,
|
||||
`INSERT DATA { <${prot}> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <${cls}> ; <urn:e2e:ws:title> "named-protected" }`,
|
||||
prot,
|
||||
);
|
||||
// PUBLIC, so its ReadCap is exposed on its Header branch at creation - the one
|
||||
// channel by which a bare reference alone opens a document, and the acquisition the
|
||||
// barrier now performs on this call.
|
||||
const pub = await storeRegistry.createEntityDoc(await storeRegistry.publicStore());
|
||||
await docs.sparqlUpdate(
|
||||
s.session_id,
|
||||
`INSERT DATA { <${pub}> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <${cls}> ; <urn:e2e:ws:title> "named-public" }`,
|
||||
pub,
|
||||
);
|
||||
setCurrentUser(null);
|
||||
return { owner, prot, pub };
|
||||
},
|
||||
/**
|
||||
* Subscribe a NAMED scope under `who` - the owner, or somebody holding nothing.
|
||||
*
|
||||
* The identity is set here and left set for the probe's lifetime, like the two
|
||||
* journeys above: `useShapeQuery` asks the keyring about whoever is connected on every
|
||||
* read, exactly as an application with one stable identity per page does.
|
||||
* `useShapeQuerySnapshot` and `useShapeQueryStop` serve this probe too.
|
||||
*/
|
||||
useShapeQueryNamedSubscribe(handle: string, who: string, doc: string, cls: string) {
|
||||
registryInternals.resetRegistryCache();
|
||||
setCurrentUser(who);
|
||||
const obs = useShapeQuery(wsShape(cls), { graphs: [doc] }) as ShapeObservable;
|
||||
const unsub = obs.subscribe(() => {});
|
||||
const initial = obs.getSnapshot();
|
||||
(window as any).__sdk._wsProbes[handle] = { obs, initial, unsub };
|
||||
return {
|
||||
initial: {
|
||||
isPending: initial.isPending,
|
||||
isSuccess: initial.isSuccess,
|
||||
isError: initial.isError,
|
||||
},
|
||||
};
|
||||
},
|
||||
/** The error message a named scope published, so the runner can tell the refusal from
|
||||
* any other failure rather than accepting `isError` as such. */
|
||||
useShapeQueryError(handle: string) {
|
||||
const rec = (window as any).__sdk._wsProbes[handle];
|
||||
if (!rec) return null;
|
||||
const snap: ShapeQuery = rec.obs.getSnapshot();
|
||||
return snap.error === undefined ? null : String((snap.error as Error)?.message ?? snap.error);
|
||||
},
|
||||
|
||||
/**
|
||||
* EMPTY-scope proof: a brand-new identity with NO entity docs at all. `useShapeQuery`
|
||||
* must reach isSuccess with data:[] (synced-but-empty), NOT stay pending. Returns
|
||||
|
||||
Reference in New Issue
Block a user