docs: chaque symbole dit d'où il vient

98 annotations posées à côté des déclarations, et un test qui les exige sur la
surface publiée. Elles portent trois choses : le niveau qui répond, la référence
amont, et la catégorie parmi les cinq.

La cinquième est celle qui manquait : declared-not-wired, quand la cible DÉFINIT
la forme et ne la câble pas. Neuf symboles en relèvent, dont readLinks — que
j'avais classé « notre invention » en raisonnant depuis l'absence, alors que
c'est le meilleur alignement disponible.

Les références citent un SYMBOLE, jamais une ligne : trois citations du document
avaient déjà pourri. Cinq corrections au passage, toutes vérifiées à la source —
un chemin ORM qui n'existe pas, deux plages de lignes fausses, et surtout
docs.* et subscribeDoc étiquetés PASSTHROUGH alors qu'ils sont alignés : nos
noms, plus un argument jamais transmis. La sémantique survit à la migration,
les sites d'appel non, et la nuance disparaissait sous une étiquette trop
flatteuse.

Le test échoue à l'annotation retirée, à la catégorie mal orthographiée, et à
une invention qui prétendrait citer une référence — vérifié en cassant les
trois. Il a aussi attrapé un défaut en lui-même : le gabarit de format placé
dans index.ts se faisait analyser comme une annotation.

La classification couvre l'interne qui prétend ressembler à la cible — tout
emulated-verifier — et exclut ce qui ne le prétend pas. La faute d'origine
portait sur une fonction non exportée ; n'être pas publié n'a protégé personne.

Quatre symboles ont résisté et sont annotés avec leur catégorie dominante, la
seconde nommée dans la note plutôt que lissée.
This commit is contained in:
Sylvain Duchesne
2026-08-16 22:53:50 +02:00
parent 6138d831da
commit 43aadbeb45
26 changed files with 353 additions and 8 deletions
+3
View File
@@ -51,6 +51,7 @@ function rowCount(result: unknown): number {
// through this chain — never normalise it, and above all never stringify it: a JS string
// fails that deserialization, observed live as
// `Deserialization error of session_id JsValue("1")`.
// @provenance docs.docCreate kind=aligned level=2 ref=sdk/js/lib-wasm/src/lib.rs:doc_create — same arguments in the same order; JS-cased name, and the broker's answer is validated into a `Nuri`
export async function docCreate(
sessionId: string | number,
crdt: string,
@@ -98,6 +99,7 @@ export async function docCreate(
* {@link sparqlQuery} already renders the same upstream `Promise<any>`: the value is
* the broker's to shape, and a caller that ignores it is unaffected.
*/
// @provenance docs.sparqlUpdate kind=aligned level=2 ref=sdk/js/lib-wasm/src/lib.rs:sparql_update — same arguments; the trailing `label` is a lib-internal access-log tag, never forwarded
export async function sparqlUpdate(
sessionId: string | number,
query: string,
@@ -131,6 +133,7 @@ export async function sparqlUpdate(
* Mirrors `ng.sparql_query(session_id, query, base?, anchor?)`. `base` is the
* query base IRI (usually `undefined`); `anchor` is the document NURI to query.
*/
// @provenance docs.sparqlQuery kind=aligned level=2 ref=sdk/js/lib-wasm/src/lib.rs:sparql_query — same arguments; the trailing `label` is a lib-internal access-log tag, never forwarded
export async function sparqlQuery(
sessionId: string | number,
query: string,
+10
View File
@@ -58,6 +58,7 @@ import type { Nuri, NuriLike, PrincipalId, ReadCap } from "../model/types";
// --- deposit model --------------------------------------------------------
/** One deposit as materialized from an inbox document. */
// @provenance inbox.Deposit kind=aligned level=1 ref=engine/net/src/types.rs:InboxMsgBody — `from` optional like `from_inbox`, and NO target-document field because the address identifies the recipient repo; `payload`/`ts` have no upstream field
export interface Deposit {
/** The sender, if identified; `null` when the deposit was anonymous. */
from: PrincipalId | null;
@@ -68,6 +69,7 @@ export interface Deposit {
}
/** Options for {@link post}. `from` and `ts` are both optional. */
// @provenance inbox.PostOptions kind=aligned level=1 ref=engine/net/src/types.rs:InboxMsgBody — same shape as Deposit on the way in; `from: null` mirrors `from_inbox: None`
export interface PostOptions {
/**
* Who is depositing. Omit (or pass `null`) for an ANONYMOUS deposit; pass a
@@ -169,6 +171,7 @@ function readBindings(result: unknown): Array<Record<string, { value: string }>>
* another's identity. This check is redundant once the seal enforces it, but
* until then it closes the spoof the shared wallet would otherwise allow.
*/
// @provenance inbox.post kind=aligned level=1 ref=engine/verifier/src/inbox_processor.rs:post_to_inbox — the act exists at level 1 and the broker routes it (`inbox_post`); no JS surface at level 2/3. An arbitrary `payload` is a BET — `InboxMsgContent` has no generic variant
export async function post(targetInboxLike: NuriLike, opts: PostOptions): Promise<void> {
const targetInbox = toNuri(targetInboxLike, "inbox.post");
const current = getCurrentUser();
@@ -269,6 +272,7 @@ export async function post(targetInboxLike: NuriLike, opts: PostOptions): Promis
* address in advance, because an application must name a document or a person, never an
* inbox.
*/
// @provenance inbox.postToDocument kind=declared-not-wired level=1 ref=engine/repo/src/repo.rs:Repo — every repo carries `inbox: Option<PrivKey>`, but nothing opens one for a plain document. Resolving the address by READING the document is our divergence — see `publishInboxAddress`
export async function postToDocument(docLike: NuriLike, opts: PostOptions): Promise<void> {
const doc = toNuri(docLike, "inbox.postToDocument");
const target = await documentInboxAddress(doc);
@@ -364,6 +368,7 @@ function capOfPayload(payload: unknown): ReadCap | null {
* The shape is right; the implementation is absent at both ends, so we emulate it
* meanwhile.
*/
// @provenance inbox.share kind=declared-not-wired level=1 ref=engine/net/src/types.rs:ContactDetails — `read_cap: Option<ReadCap>` is defined; building the message is `unimplemented!()`, its only caller passes `with_readcap: false`, and the receiving arm never reads the field
export async function share(doc: NuriLike, toUser: string): Promise<void> {
const target = toNuri(doc, "inbox.share");
// Names the DOCUMENT and the PERSON — the two things an application has. Neither the
@@ -431,6 +436,7 @@ export async function share(doc: NuriLike, toUser: string): Promise<void> {
* no more reason to handle an inbox address than a depositor does. Empty when the
* document has no inbox, which is a state and not an error.
*/
// @provenance inbox.readForDocument kind=divergent level=1 ref=engine/verifier/src/inbox_processor.rs:process_inbox — upstream an inbox is a queue the verifier consumes and APPLIES; this enumerates it instead, non-destructively
export async function readForDocument(docLike: NuriLike): Promise<Deposit[]> {
const doc = toNuri(docLike, "inbox.readForDocument");
const address = await documentInboxAddress(doc);
@@ -484,6 +490,7 @@ async function assertOwnInbox(targetInbox: Nuri, op: string): Promise<void> {
* watching its inbox gets them, and the resulting change re-triggers the
* reads that were empty for want of that cap.
*/
// @provenance inbox.read kind=divergent level=1 ref=engine/verifier/src/inbox_processor.rs:process_inbox — same: a re-readable list where upstream has a consumed queue. Coding a mailbox UI on this is coding against emulation detail
export async function read(targetInboxLike: NuriLike): Promise<Deposit[]> {
const targetInbox = toNuri(targetInboxLike, "inbox.read");
await assertOwnInbox(targetInbox, "read");
@@ -578,6 +585,7 @@ export async function read(targetInboxLike: NuriLike): Promise<Deposit[]> {
* `discovery.readIndex` does. Idempotent per session (no polling); a no-op open on
* the unit fake-ng path (no `doc_subscribe`) so `bun test` is unaffected.
*/
// @provenance inbox.readSynced kind=divergent level=1 ref=engine/verifier/src/inbox_processor.rs:process_inbox — same as `read`, gated on the sync barrier first
export async function readSynced(targetInboxLike: NuriLike): Promise<Deposit[]> {
const targetInbox = toNuri(targetInboxLike, "inbox.readSynced");
// Marks the cold, connection-triggered entry point in the trace — the BARRIER
@@ -604,6 +612,7 @@ export async function readSynced(targetInboxLike: NuriLike): Promise<Deposit[]>
* second tab, a reconnect) costs nothing. Returns the consumer deposits, exactly as
* {@link read} does — Links are never surfaced.
*/
// @provenance inbox.processInbox kind=aligned level=1 ref=engine/verifier/src/inbox_processor.rs:process_inbox — unseal-and-apply: Links are filed durably and never surfaced. Returning the remaining deposits is the divergent half it shares with `read`
export async function processInbox(targetInboxLike: NuriLike): Promise<Deposit[]> {
const targetInbox = toNuri(targetInboxLike, "inbox.processInbox");
const deposits = await readSynced(targetInbox);
@@ -711,6 +720,7 @@ async function processForOwner(targetInbox: Nuri): Promise<void> {
* there is no polling. (The inbox document is a single doc, so this is immune to
* the ORM fan-out hang — see {@link subscribeDoc}.)
*/
// @provenance inbox.watch kind=divergent level=1 ref=engine/verifier/src/verifier.rs:inbox — upstream a message is applied on arrival and there is nothing to subscribe to; this pushes the raw deposit list on every change
export function watch(
targetInboxLike: NuriLike,
onDeposits: (deposits: Deposit[]) => void,
@@ -58,6 +58,7 @@ import { captureSession, expectSession } from "../shared-wallet/session";
* it (`callback: Function | null`, and the call site is guarded). The wrapper still goes in,
* so the package gets its session either way, and calls nothing afterwards.
*/
// @provenance init kind=passthrough level=2 ref=sdk/js/web/src/index.ts:init — arguments and return pass straight through; the callback in position 0 is wrapped to keep the session it carries
export function init(...args: any[]): any {
const f = getConfig().init;
if (!f) throw new Error("[ng-eventually] init() not injected — pass it to configure()");
@@ -90,6 +91,7 @@ export function init(...args: any[]): any {
}
/** Forwards to the real `@ng-org/orm` `initNg` (ORM signals). */
// @provenance initNg kind=passthrough level=3 ref=sdk/js/orm/src/core.ts:initNg — upstream's own exported name for `initNgSignals`
export function initNg(...args: any[]): any {
const f = getConfig().initNg;
if (!f) throw new Error("[ng-eventually] initNg() not injected — pass it to configure()");
@@ -61,16 +61,19 @@ function connectedIdentity(op: string): string {
}
/** Create a document for ONE entity in `scope`, and record it in that scope's store. */
// @provenance storeRegistry.createEntityDoc kind=aligned level=2 ref=sdk/js/lib-wasm/src/lib.rs:doc_create — upstream `doc_create` performs the `ldp:contains` listing and the `AddRepo { read_cap }` natively; here they are two explicit writes
export async function createEntityDoc(scope: Scope): Promise<Nuri> {
return registryCreateEntityDoc(connectedIdentity("createEntityDoc"), scope);
}
/** The entity documents this user owns in `scope` — with their caps recovered. */
// @provenance storeRegistry.listMyEntityDocs kind=aligned level=1 ref=engine/verifier/src/request_processor.rs:doc_create — reads back the `ldp:contains` listing that `doc_create` writes on the store's Main branch
export async function listMyEntityDocs(scope: Scope): Promise<Nuri[]> {
return registryListMyEntityDocs(connectedIdentity("listMyEntityDocs"), scope);
}
/** The NURI where GROUPED entities of `scope` are written (no per-entity document). */
// @provenance storeRegistry.resolveWriteGraph kind=aligned level=2 ref=sdk/js/web/src/index.ts:Session — a scope resolves to the session's own store id; upstream the store IS the container and the per-scope index document disappears
export async function resolveWriteGraph(scope: Scope): Promise<Nuri> {
return registryResolveWriteGraph(connectedIdentity("resolveWriteGraph"), scope);
}
@@ -57,6 +57,7 @@ void docCreate;
void sparqlUpdate;
/** One subject read from a doc, with its properties (predicate → values). */
// @provenance UnionSubject kind=invention level=none ref=none — no upstream type groups triples per (document, subject); a polyfill property bag, to be mapped into app types at the boundary
export interface UnionSubject {
/**
* The subject IRI (`?s`), exactly as the document carries it.
@@ -163,6 +164,7 @@ async function readDoc(
* read with an anchored default-graph query, O(1) per doc, independent of wallet
* size — a non-empty wallet no longer matters. Reads run in parallel via `Promise.all`.
*/
// @provenance readUnion kind=aligned level=2 ref=sdk/js/lib-wasm/src/lib.rs:sparql_query — one anchored query per document — upstream's own anchoring (`resolve_target_for_sparql`), composed client-side
export async function readUnion(docsLike: NuriLike[]): Promise<UnionSubject[]> {
const sid = await sessionId();
// Drop the empties BEFORE validating, not after: this call has always tolerated a
@@ -45,6 +45,7 @@ import type { Nuri, NuriLike } from "../model/types";
* change SIGNAL (re-query on change — the read-model pattern), so this stays
* permissive rather than modelling every AppResponse variant.
*/
// @provenance DocChange kind=aligned level=1 ref=engine/net/src/app_protocol.rs:AppResponseV0 — the raw serialized response, left `unknown` rather than modelling every variant
export type DocChange = unknown;
/**
@@ -58,6 +59,7 @@ export type DocChange = unknown;
* `TabInfo`/`Patch` pushes — see `open-repo.ts`. Most callers ignore it and use
* any push as a plain change signal.
*/
// @provenance DocChangeType kind=aligned level=1 ref=engine/net/src/app_protocol.rs:AppResponseV0 — the variant discriminant; a bare string because the enum is open (State | Patch | TabInfo | …)
export type DocChangeType = string | undefined;
/**
@@ -67,6 +69,7 @@ export type DocChangeType = string | undefined;
* is not a recognisable `{ V0: { <Variant>: … } }` object. Inspects the variant
* proplerly (no `any`-cast to force it) so a `State` push is identifiable.
*/
// @provenance docChangeType kind=aligned level=1 ref=engine/net/src/app_protocol.rs:AppResponseV0 — reads the variant key out of the payload — a convenience over a verified shape, not an upstream call
export function docChangeType(resp: DocChange): DocChangeType {
if (!resp || typeof resp !== "object") return undefined;
const outer = resp as { V0?: unknown; v0?: unknown };
@@ -77,6 +80,7 @@ export function docChangeType(resp: DocChange): DocChangeType {
}
/** An unsubscribe function — idempotent (calling it twice is a no-op). */
// @provenance Unsubscribe kind=aligned level=2 ref=sdk/js/lib-wasm/src/lib.rs:doc_subscribe — upstream resolves to this function; ours is returned synchronously
export type Unsubscribe = () => void;
async function sessionId(): Promise<string | number> {
@@ -102,6 +106,7 @@ async function sessionId(): Promise<string | number> {
*
* Calls the REAL injected `ng.doc_subscribe` directly (never `makeNg`).
*/
// @provenance subscribeDoc kind=aligned level=2 ref=sdk/js/lib-wasm/src/lib.rs:doc_subscribe — two deliberate deltas: the unsubscribe is returned synchronously, and the callback gets a pre-extracted `type`
export function subscribeDoc(
nuriLike: NuriLike,
onChange: (r: DocChange, type: DocChangeType) => void,
@@ -118,6 +123,7 @@ export function subscribeDoc(
* owns the machinery's entire privileged door — and for nobody else. It is not
* re-exported by either entry point; the `Unguarded` suffix is the warning.
*/
// @provenance subscribeDocUnguarded kind=aligned level=2 ref=sdk/js/lib-wasm/src/lib.rs:doc_subscribe — the same call without the reach guard; internal, never published
export function subscribeDocUnguarded(
nuri: Nuri,
onChange: (r: DocChange, type: DocChangeType) => void,
@@ -177,6 +183,7 @@ export function subscribeDocUnguarded(
* ORM fan-out hang — do NOT replace this with `orm_start_graph(graphs:[…])`). The
* set is deduplicated; an empty set returns a no-op unsubscribe.
*/
// @provenance subscribeDocs kind=aligned level=2 ref=sdk/js/lib-wasm/src/lib.rs:doc_subscribe — client-side composition with per-doc error isolation; `orm_start_graph` is deliberately unused — it aborts wholesale on one RepoNotFound
export function subscribeDocs(
nuris: Nuri[],
onChange: (nuri: Nuri, r: DocChange, type: DocChangeType) => void,
@@ -9,6 +9,7 @@
import { getConfig, getCaps } from "../shared-wallet/bootstrap";
import { makeReadFilteredView } from "../emulated-verifier/read-filter";
// @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;
const caps = getCaps();
@@ -70,6 +70,7 @@ const RDF_TYPE = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type";
* the generic per-subject property bags of the read-model (NO application domain);
* the app maps them to its own entity types in phase B.
*/
// @provenance ShapeQuery kind=invention level=none ref=none — no upstream type carries load state; the distinction IS expressible (`OrmSubscription.readyPromise`) but this vocabulary is ours
export interface ShapeQuery<T = UnionSubject> {
/** The subjects of the requested shape/scope. Empty array when none (never undefined). */
data: T[];
@@ -87,6 +88,7 @@ export interface ShapeQuery<T = UnionSubject> {
}
/** The observable a caller binds with `useSyncExternalStore` (phase B). */
// @provenance ShapeObservable kind=invention level=none ref=none — no upstream observable exists; the ORM exposes a hook returning a DeepSignalSet and nothing else
export interface ShapeObservable<T = UnionSubject> {
/** The current snapshot. STABLE across calls until it actually changes (so
* `useSyncExternalStore` does not loop): the same reference is returned until a
@@ -163,6 +165,7 @@ function barrierReached(docs: Nuri[]): boolean {
* what kicks off resolution, opening and the first read; before then `getSnapshot`
* reports the initial pending snapshot.
*/
// @provenance watchShape kind=invention level=none ref=none — nothing upstream distinguishes syncing from synced-empty at the hook; the header's 'planned useShape upgrade' has NO upstream provenance
export function watchShape<T = UnionSubject>(
shapeType: unknown,
scope: Scope,