Les quatre paquets @ng-org sont deja a leur derniere version publiee
(web alpha.14, orm alpha.21, shex-orm alpha.9, alien-deepsignals
alpha.13) : le refresh ne deplace rien. Ce qui restait faux, c'est la
doctrine, laissee en arriere par le refresh precedent.
Le point de fond : le checkout epingle de nextgraph-rs (213338f6)
declare orm alpha.18, donc la source citee est TROIS versions derriere
les paquets contre lesquels ce depot compile. La ou les deux divergent,
les leaves nomment desormais les deux.
Faits corriges, tous verifies dans node_modules :
- les versions installees annoncees en tete de leaf (alpha.19/13/11)
- OrmSubscription est renomme RdfOrmSubscription dans alpha.21, fichier
compris (connector/RdfOrmSubscription.ts)
- alpha.21 exporte removeObject(graphNuri, subjectIri), absent de la
source ; sa propre documentation se contredit sur sa portee
- getOrCreate prend un RdfOrmConfig, plus un Scope nu, et l'instance
gagne mode/isReady/addChangeListener/nextPage/previousPage
- Scope.graphs est REQUIS dans alpha.21, et les deux axes lisent [] en
sens INVERSE : graphs [] = ensemble vide, subjects [] = aucun filtre.
La table le dit explicitement, c'est le piege deja paye une fois
- la lecture ORM live DEMARRE contre le deploiement (5 passed, 0 failed,
pret en 13ms) ; le lien mort vers bug_useshape-live-read-needs-a-newer
-broker disparait, ainsi que le "exit non-zero attendu" de la suite
Ce qui n'est PAS affirme : que le where/orderBy/pageSize d'alpha.21
atteigne le moteur. A 213338f6 start_orm construit encore son config a
partir de json!({}) derriere un TODO, donc la richesse est exprimable au
niveau 3 et jetee au niveau 1. Non mesure, donc non ecrit.
9.7 KiB
type, summary, last_checked
| type | summary | last_checked |
|---|---|---|
| knowledge | What the target offers for writing and removing — deletion is a SET MUTATION wired end to end, it removes the shape's predicates rather than the subject, and the ORM's writes never pass through `sparql_update` | 2026-09-21 |
What the ORM writes and deletes upstream
Read at the source in nextgraph-rs (213338f697df15afc086c042f4bab1bffd01aa04), which declares @ng-org/orm@0.1.2-alpha.18, @ng-org/web@0.1.2-alpha.13, @ng-org/shex-orm@0.1.2-alpha.8 and @ng-org/alien-deepsignals@0.1.2-alpha.11. packages/polyfill installs @ng-org/orm@0.1.2-alpha.21, @ng-org/web@0.1.2-alpha.14, @ng-org/shex-orm@0.1.2-alpha.9 and @ng-org/alien-deepsignals@0.1.2-alpha.13 - the published packages are AHEAD of the source this repo cites, so where the two disagree both are named below. Every claim points at a symbol rather than a line, so git grep settles any doubt after the code moves. Companion to knowledge_what-a-read-scope-is-upstream, which answers the reading half.
Removing an object is a set mutation, and LEVEL 3 answers
sdk/js/orm/README.md teaches it as one line — dogs.delete(aDog) — on the DeepSignalSet a subscription hands back. There is no deleteObject symbol at any version: sdk/js/orm/src/core.ts at the cited commit exports exactly initNg, ngSession, OrmSubscription, DiscreteOrmSubscription, insertObject, getObjects, plus getRaw/watch/effect re-exported from @ng-org/alien-deepsignals. The installed @ng-org/orm@0.1.2-alpha.21 renames OrmSubscription to RdfOrmSubscription and adds one export the source does not carry, removeObject(graphNuri, subjectIri): Promise<void> - see the note at the end of this section. That absence is not the finding, and per no-divergence-from-nextgraph it would prove nothing on its own — the finding is that the set mutation is fully implemented, so the cascade stops at level 3 and there is nothing to invent.
removeObject exists in the package and not in the source. Its own documentation declares a wider effect than the set mutation: DELETE WHERE { GRAPH <graphNuri> { <subjectIri> ?p ?o } }, every predicate of the subject rather than the shape's own, while claiming in the same comment to have "the same effect" as a delete() on a subscription's object. The two cannot both hold, and nothing in the cited source settles it. It stays unpublished here for the reason contract_polyfill-surface records: a symbol whose only evidence is an npm build is not something to build a guarantee on.
The path, hop by hop
- The set proxy.
deepSignal's set handler (sdk/js/alien-deepsignals/src/deepSignal.ts, thedeletebranch) emits, for an OBJECT member,{ path: [...containerPath, syntheticId], op: "remove" }— aDeepPatchwith notype. For a LITERAL inside a multi-valued property it emits{ path: containerPath, op: "remove", type: "set", value }instead. - The synthetic id is
"<graph>|<subject>", minted byOrmSubscription.signalObjectPropGenerator(RdfOrmSubscriptionin alpha.21), which also generates a subject IRI (<first 53 chars of the graph NURI>:q:<33 random bytes base64url>) when@idis"". deepPatchesToWasm(sdk/js/orm/src/connector/utils.ts) joins the path into the string/<graph>|<subject>.OrmSubscription.onSignalObjectUpdate(RdfOrmSubscriptionin alpha.21) batches in a microtask (or intopendingPatchesinside a transaction) and callsng.graph_orm_update(subscriptionId, patches, session_id).- The binding.
graph_orm_update(sdk/js/lib-wasm/src/lib.rs) deserialisesOrmPatches—OrmPatch { op, valType, path, value }withOrmPatchOp::{add, remove}andOrmPatchType::set(engine/net/src/orm.rs) — intoAppRequest::new_orm_update. - The engine.
Verifier::orm_frontend_update→create_sparql_update_query_for_patches(engine/verifier/src/orm/graph/handle_frontend_update.rs): aremovewhose path resolves to a ROOT object (one segment, no predicate) callsSparqlBuilder::remove_objectand marks the tracked objectTrackedOrmObjectValidity::Invalid. The queries are then run throughprocess_sparql_update, anchored on the graph taken from the first patch's path.
The trap that makes a delete a no-op. The patch is emitted only when target.delete(rawValue) returned true and a synthetic id is known for that object. So the object passed must be the one the set is holding — reached by iterating it, by .first() or by .getBy(graph, subject). getObjects returns a deepClone, so deleting an item of ITS result removes nothing and reports nothing.
remove_object deletes the SHAPE's predicates, not the subject and not the document
SparqlBuilder::remove_object loops the shape's own predicates and emits one DELETE { GRAPH <g> { <s> <p> ?o } } WHERE { … } per predicate. Triples on the same subject under a predicate the shape does not declare survive, and so does the document: nothing on this path deletes a document, and the ORM offers no call that does.
Removing a child from a parent's multi-valued object property is a different arm — remove_link, a DELETE DATA of the one triple that ties parent to child. The README states the consequence plainly: only that tie goes, the child's own quads stay.
getObjects and insertObject — the exact surface
getObjects<T extends BaseType>(shapeType: ShapeType<T>, scope: Scope | string): Promise<DeepSignalSet<T>>
insertObject<T extends BaseType>(shapeType: ShapeType<T>, object: T): Promise<void>
Both return types are inferred, not annotated, and both functions are thin wrappers over OrmSubscription.
getObjects(sdk/js/orm/src/connector/getObjects.ts) —getOrCreate(shapeType, normalizeScope(scope)), awaitsreadyPromise, schedulesclose()one second later, returnsdeepClone(signalObject). The result is detached: a plainSetof plain objects, no two-way binding, no reactivity.docs/api-contract.md§ 6 records this accurately — checked against the source, not re-derived.insertObject(sdk/js/orm/src/connector/insertObject.ts) — opens a subscription scoped to{ graphs: [] }(deliberately subscribing to no document), awaits ready,signalObject.add(object),close(). The object must carry@graph: a root object without one makessignalObjectPropGeneratorthrow "When adding new root orm objects, you must specify the @graph".@id: ""means "generate one".
The three remaining symbols
OrmSubscription, RdfOrmSubscription in alpha.21 — a class, and the machinery every other read and write goes through. Private constructor; static getOrCreate(shapeType, scope) pooled on "<shape>|<graphs>|<subjects>" with refcounting and a 500 ms close delay. Instance surface: signalObject: DeepSignalSet<T>, readyPromise, close(), beginTransaction(), commitTransaction(), inTransaction, shapeType, scope. In alpha.21 the second argument is no longer a bare Scope but an RdfOrmConfig (Scope & { where?, orderBy?, pageSize?, maxActivePages? }), readyPromise resolves to the subscription data rather than to nothing, and the instance also carries mode, isReady, addChangeListener, removeChangeListener, nextPage and previousPage. Whether any of that reaches the engine is NOT settled by the cited source, where Verifier::start_orm still builds its config from json!({}) behind a // TODO (engine/verifier/src/orm/graph/initialize.rs): as of 213338f6 the richness is expressible at level 3 and discarded at level 1. Measuring it is not done here. It hands a caller nothing the target will not hand them — a shape, a scope and a set — and useShape, getObjects and insertObject are all built on it.
DiscreteOrmSubscription — the same class shape for JSON-CRDT documents. static getOrCreate(documentId: string); signalObject is DeepSignal<DiscreteArray | DiscreteObject> | undefined (a getter, undefined before the initial payload); it reaches the engine through orm_start_discrete and discrete_orm_update. Its unit of subscription is one document id, never a Scope.
ngSession — not a function and not a class: a module-level Promise<{ ng: NG; session: Session }> (sdk/js/orm/src/connector/initNg.ts), resolved exactly once by initNg. Every subscription awaits it before touching the engine. Its value is the SDK handle plus the session object.
A write never passes through sparql_update
graph_orm_update is its own SDK member, distinct from sparql_update. So a guard implemented by overriding ng.sparql_update — which is what packages/polyfill/src/surface/ng-proxy.ts does — does not see an ORM write at all, neither an add nor a delete. The read-filtered view (packages/polyfill/src/emulated-verifier/read-filter.ts) is symmetrical by design: it forwards add, delete and clear to the underlying set untouched, because those yield no items to leak.
That is a fact about where a guard can sit, not a proposal for one.
Live behaviour against the deployed broker
Verified by measurement, not by reading: a live ORM read STARTS against the deployment, and the delete reaches the document. packages/polyfill/e2e/orm-live-read.ts is the measurement - 5 passed, 0 failed on 2026-09-21, the read ready in 13ms, the seeded subject's triples gone after .delete().
The read was blocked until 2026-09-21, and the recorded reason was wrong twice over. It was not that the broker needed to catch up: the DEPLOYMENT was ahead and the pinned client packages were behind, orm_start_graph having gained a config parameter at index 4, before the callback, which the five-argument client of the day filled with its callback. Updating the packages answered it. The bug leaf that carried the wrong mechanism was deleted with the question it described.