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.
This commit is contained in:
@@ -36,6 +36,9 @@ import {
|
||||
// application must not — but through the internal path, never the published entry.
|
||||
// `storeRegistry` above is the app-facing slice; these are the shim internals.
|
||||
import * as registryInternals from "../src/shared-wallet/account-registry";
|
||||
// The shared `useShape` fake, the same one `bun test` injects. Type-only imports
|
||||
// inside it, so nothing of `bun:test` reaches this bundle.
|
||||
import { makeUseShapeHook } from "../test/use-shape-fake";
|
||||
// The harness plays SEVERAL identities on one page — something no application does, and
|
||||
// the reason `setCurrentUser` / `configureStoreRegistry` are no longer published. It
|
||||
// reaches them by their internal path, like the rest of its machinery.
|
||||
@@ -188,26 +191,18 @@ let session: BrokerSession | null = null;
|
||||
let sessionResolve!: (s: BrokerSession) => void;
|
||||
const sessionReady = new Promise<BrokerSession>((r) => (sessionResolve = r));
|
||||
|
||||
// A minimal in-memory Set-like, so the lib's read-filtered `useShape` view can be
|
||||
// exercised WITHOUT the real ORM (`@ng-org/orm` is not installed here, and the
|
||||
// read-filter is pure — it wraps whatever Set-like the injected useShape returns).
|
||||
// The injected useShape receives `(shapeType, scope)`; we ignore both and return
|
||||
// the items array captured by the test through window.__sdk.caps.seedSet().
|
||||
let injectedSetItems: any[] = [];
|
||||
function fakeUseShape(_shape: unknown, _scope: unknown): any {
|
||||
const items = () => injectedSetItems;
|
||||
return {
|
||||
get size() { return items().length; },
|
||||
[Symbol.iterator]() { return items()[Symbol.iterator](); },
|
||||
forEach(cb: (v: unknown) => void) { items().forEach(cb); },
|
||||
add(item: any) { injectedSetItems.push(item); },
|
||||
};
|
||||
}
|
||||
// The injected `useShape`, so the lib's read-filtered view can be exercised WITHOUT a
|
||||
// React tree. It is `test/use-shape-fake.ts` — the ONE fake in this repository that
|
||||
// returns the shape `@ng-org/orm`'s hook really ships, read off the installed package's
|
||||
// own `.d.ts`. This page used to carry a hand-written Set-like of its own, which is
|
||||
// exactly why the capability filter could hand three members of that envelope back raw
|
||||
// and still pass every gate: no fake anywhere said what the hook returns.
|
||||
const shapeHook = makeUseShapeHook();
|
||||
|
||||
// ── Inject the real SDK + polyfill settings (the consumer bootstrap) ────────
|
||||
configure({
|
||||
ng: realNg,
|
||||
useShape: fakeUseShape,
|
||||
useShape: shapeHook.hook,
|
||||
// The SDK's own shape query, passed as it stands - no wrapper, no cast. This is what
|
||||
// `useShapeQuery` reads through, so the journeys below run against the REAL broker
|
||||
// rather than against anything this page composed.
|
||||
@@ -1083,26 +1078,42 @@ const identity = new IdentityStore(
|
||||
// cap-surface is KEY POSSESSION — you read what your keyring holds, nothing else.
|
||||
capsReadFilter() {
|
||||
resetCaps();
|
||||
injectedSetItems = [
|
||||
shapeHook.items.length = 0;
|
||||
shapeHook.items.push(
|
||||
{ "@graph": "did:ng:o:protdoc", "@id": "1", v: "protected-item" },
|
||||
{ "@graph": "did:ng:o:pubdoc", "@id": "2", v: "public-item" },
|
||||
{ "@graph": "did:ng:o:unheld", "@id": "3", v: "unheld-item" },
|
||||
];
|
||||
);
|
||||
// What the hook hands back is the SDK's result envelope, so the items are read off
|
||||
// `data` - the member its own example destructures first, and one of the three that
|
||||
// used to come back unfiltered.
|
||||
const read = (): string[] =>
|
||||
[...(libUseShape(wsShape("urn:e2e:caps:Item"), EVERYTHING).data as Iterable<any>)].map(
|
||||
(i) => i.v,
|
||||
);
|
||||
setCurrentUser("owner-O");
|
||||
getCaps().open("did:ng:o:protdoc", "protected");
|
||||
const link = getCaps().open("did:ng:o:pubdoc", "public");
|
||||
const ownerView = [...(libUseShape(null, null) as Iterable<any>)].map((i) => i.v);
|
||||
const ownerView = read();
|
||||
// A stranger holds nothing, and this VIEW asks nobody: it is pure possession, with
|
||||
// no round-trip to spend (see `read-filter.ts`). That a public store would serve
|
||||
// the cap is proven on the read paths, not here.
|
||||
setCurrentUser("stranger");
|
||||
const strangerView = [...(libUseShape(null, null) as Iterable<any>)].map((i) => i.v);
|
||||
const strangerView = read();
|
||||
// …and the SDK's own handle is refused outright, whoever asks: it reads and writes
|
||||
// with no capability filter at all.
|
||||
let subscriptionRefused: string | null = null;
|
||||
try {
|
||||
void libUseShape(wsShape("urn:e2e:caps:Item"), EVERYTHING).subscription;
|
||||
} catch (e) {
|
||||
subscriptionRefused = e instanceof Error ? e.message : String(e);
|
||||
}
|
||||
getCaps().learn(link);
|
||||
const strangerWithLinkView = [...(libUseShape(null, null) as Iterable<any>)].map((i) => i.v);
|
||||
const strangerWithLinkView = read();
|
||||
resetCaps();
|
||||
injectedSetItems = [];
|
||||
shapeHook.items.length = 0;
|
||||
setCurrentUser(null);
|
||||
return { ownerView, strangerView, strangerWithLinkView };
|
||||
return { ownerView, strangerView, strangerWithLinkView, subscriptionRefused };
|
||||
},
|
||||
// MOVED to the applicative suite, `e2e/notebook.ts` (2026-08-07):
|
||||
// - `documentInboxDeposit` → "Bob leaves a message on Alice's note, and only Alice
|
||||
|
||||
Reference in New Issue
Block a user