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:
Sylvain Duchesne
2026-09-24 18:27:02 +02:00
parent 5993a8c4a2
commit 3a8941159f
13 changed files with 817 additions and 94 deletions
+28 -11
View File
@@ -1,18 +1,35 @@
/**
* Wrapped `useShape`: same signature as `@ng-org/orm`. Once the cap emulation is
* in force, the returned set is a read-filtered VIEW (only items in documents the
* current holder has the ReadCap of); before the first cap is issued it passes the
* real set through unchanged. At migration the filtering disappears — the broker
* only delivers documents whose cap the wallet holds.
* Wrapped `useShape`: the same call the SDK's React hook offers, and the same two
* arguments. Once the cap emulation is in force, what comes back is a read-filtered VIEW
* over the RESULT ENVELOPE the hook returns - `data` and `promise` carry only items in
* documents the current holder has the `ReadCap` of, `subscription` is refused, and the
* page commands and the load state pass through. Before the first cap is issued the real
* result passes through unchanged. At migration the filtering disappears: the broker only
* delivers documents whose cap the wallet holds.
*
* ── What changed on 2026-09-24, and why it was invisible ──────────────────
* This wrapped the result in the COLLECTION view (`makeReadFilteredView`), which is
* written for a `DeepSignalSet`. That was right while the hook returned one - `alpha.18` -
* and wrong the moment `@ng-org/orm@0.1.2-alpha.21` started returning a `UseShapeResult`
* around it. Against that shape the collection view threw on `[...result]` and `.size`
* (an envelope is not iterable) and returned `data`, `promise` and `subscription` RAW,
* each of them every identity's items. Nothing failed, because no fake in this repository
* returned the shape the SDK ships.
*/
import type { BaseType, ShapeType } from "@ng-org/shex-orm";
import type { RdfOrmConfig } from "@ng-org/orm/all";
import { getConfig, getCaps } from "../shared-wallet/bootstrap";
import { makeReadFilteredView } from "../emulated-verifier/read-filter";
import { makeReadFilteredShapeResult } from "../emulated-verifier/read-filter";
import type { ShapeResult } from "../model/types";
// @provenance useShape kind=passthrough level=3 ref=sdk/js/orm/src/frontendAdapters/react/useShape.ts:useShape — forwards to the injected real hook; the read-filtered view emulates cryptographic isolation and the generic `T` is widened to `unknown`
export function useShape(shapeType: unknown, scope: unknown): unknown {
const set = getConfig().useShape(shapeType, scope) as object;
// @provenance useShape kind=passthrough level=3 ref=sdk/js/orm/src/frontendAdapters/react/useShape.ts:useShape — forwards to the injected real hook, with its two arguments; the read-filtered view over the result envelope emulates cryptographic isolation
export function useShape<T extends BaseType>(
shapeType: ShapeType<T>,
conf: RdfOrmConfig<T> | string | undefined,
): ShapeResult {
const result = getConfig().useShape(shapeType, conf);
const caps = getCaps();
if (!caps.isEnforcing()) return set; // no cap issued yet → passthrough
return makeReadFilteredView(set, caps);
if (!caps.isEnforcing()) return result; // no cap issued yet → passthrough
return makeReadFilteredShapeResult(result, caps);
}