fix: quatre écarts entre la surface publiée et ce que NextGraph déclare

Un audit de la surface contre la source amont en a trouvé cinq ; voici les
quatre mécaniques. La cinquième — l'adresse d'inbox, qui traverse sept symboles
— relève du dessin et reste ouverte.

L'identifiant de session bloquait. Amont le déclare string | number
(sdk/js/web/src/index.ts:16) et le binding désérialise un u64 ; nous exigions
une chaîne. Une application ne pouvait donc pas passer la valeur que le SDK
venait de lui remettre. Élargi à ce qu'amont déclare, sur toute la chaîne, et
jamais converti : une chaîne échoue pour de vrai (Deserialization error of
session_id JsValue("1"), observé).

sparqlUpdate annonçait Promise<void> alors qu'il relayait DÉJÀ les commits.
C'était donc un mensonge de typage, pas un comportement — et la doublure de test
qui rendait undefined, un état que le vrai broker ne produit jamais, est ce qui
l'a laissé sans contradicteur.

ng était publié en Record<string, any>, ce qui perdait les 88 membres typés
d'amont — 88, pas 77 : le chiffre de notre propre documentation était faux.

Et materialize, second nom publié de read, sans appelant ni contrepartie amont,
est retiré.

docs/api-contract.md qualifiait docs.* de passthrough « 1:1 ». C'était faux sur
les deux premiers points. Corrigé, pas complété : un document qui se déclare
vérifié et qui ment est pire qu'un document absent, parce qu'on cesse d'aller
voir.

Une déviation assumée : amont type le retour en any, interdit ici ; on rend
unknown, comme sparqlQuery le fait déjà pour le même any amont.
This commit is contained in:
Sylvain Duchesne
2026-08-14 10:00:40 +02:00
parent e32b6d04fc
commit 12eba6eea6
17 changed files with 227 additions and 99 deletions
+13 -3
View File
@@ -1,5 +1,6 @@
import { test, expect, mock, beforeEach, afterAll } from "bun:test";
import { post, read, materialize, watch } from "../src/surface/inbox";
import { post, read, watch } from "../src/surface/inbox";
import * as polyfill from "../src/index";
import { userInbox, resetRegistryCache } from "../src/shared-wallet/account-registry";
import type { Deposit } from "../src/surface/inbox";
import { configure } from "../src/index";
@@ -249,14 +250,23 @@ test("from: null makes an anonymous deposit even when a current user is set", as
expect(deposits[0]!.from).toBeNull();
});
test("read returns deposits sorted by ts ascending and materialize is an alias", async () => {
test("read returns deposits sorted by ts ascending", async () => {
await post(TARGET, { from: null, payload: "second", ts: 300 });
await post(TARGET, { from: null, payload: "first", ts: 100 });
await post(TARGET, { from: null, payload: "third", ts: 500 });
const deposits = await materialize(TARGET);
const deposits = await read(TARGET);
expect(deposits.map((d) => d.payload)).toEqual(["first", "second", "third"]);
});
test("the published inbox surface exposes `read` and no `materialize` alias", () => {
// `materialize` was a second published name for `read` — a symbol an application could
// learn and would have to unlearn, since upstream has no such member. This asserts the
// PUBLISHED entry (`src/index.ts`'s `inbox` namespace), which is what an app imports,
// not merely the module it re-exports.
expect(typeof polyfill.inbox.read).toBe("function");
expect("materialize" in polyfill.inbox).toBe(false);
});
test("read is scoped to one inbox — deposits in another inbox are not returned", async () => {
// The OTHER inbox is obtained from the system, not invented. A made-up NURI would be
// a target no deposit can legitimately reach (`inbox.post` refuses what is not an