From 82769c49392d1cbe76f22062da60f6df74e2fe5f Mon Sep 17 00:00:00 2001 From: Sylvain Duchesne Date: Mon, 21 Sep 2026 11:02:08 +0200 Subject: [PATCH] chore: les paquets ng-org rattrapent le deploiement, et la lecture vive demarre --- ...useshape-live-read-needs-a-newer-broker.md | 46 ------------------- .../todo/T01-align-the-published-surface.md | 2 + packages/polyfill/e2e/orm-live-entry.ts | 39 +++++++++------- packages/polyfill/e2e/orm-live-read.ts | 12 +++-- packages/polyfill/e2e/tsconfig.json | 5 +- pnpm-lock.yaml | 11 +---- 6 files changed, 34 insertions(+), 81 deletions(-) delete mode 100644 .project/concepts/app-contract/bug_useshape-live-read-needs-a-newer-broker.md diff --git a/.project/concepts/app-contract/bug_useshape-live-read-needs-a-newer-broker.md b/.project/concepts/app-contract/bug_useshape-live-read-needs-a-newer-broker.md deleted file mode 100644 index 3b2643f..0000000 --- a/.project/concepts/app-contract/bug_useshape-live-read-needs-a-newer-broker.md +++ /dev/null @@ -1,46 +0,0 @@ ---- -type: bug -summary: A live read through the published `useShape` never starts against the deployed broker — its `orm_start_graph` expects a `config` argument the published `@ng-org/orm` does not send, so the subscription stays empty and pending for ever with one console error as the only trace -severity: major -opened: 2026-08-17 -last_checked: 2026-09-16 ---- - -# `useShape` opens no live read against the deployed broker - -The published `useShape` is a passthrough to `@ng-org/orm`'s hook (`packages/polyfill/src/surface/use-shape.ts`) — this package adds the read filter and forwards. Against the broker deployed today, the subscription that hook opens **never starts**: its set stays empty, its readiness never settles, no engine update ever arrives, and the only trace is one `console.error`. An application sees "this scope holds nothing", indefinitely, which is the exact confusion the rest of the read surface is built to prevent. - -The cause is upstream, in the disagreement between the deployed broker and the published client packages. **Nothing in this package can work around it.** - -## The mechanism — an ARGUMENT, not a name - -MEASURED 2026-09-16 against the real broker by `packages/polyfill/e2e/orm-live-read.ts`, which drives the published `useShape` over the real `@ng-org/orm`. The engine answers: - -``` -Deserialization error of config Error: invalid type: JsValue(Function), expected any valid JSON value -``` - -A live ORM read is one call and it is streamed: `OrmSubscription`'s constructor issues `ng.orm_start_graph(graphs, subjects, shapeType, session_id, onBackendMessage)` — **five** arguments, callback last — and the engine pushes into that callback rather than answering once. What comes back instead names a parameter, `config`, that the client never sent. - -The same measurement establishes where that parameter sits, by calling the SDK member directly with `{}` in position 4: **six arguments are ACCEPTED**, and the engine then streams back. So the deployed signature is `(graph_scope, subject_scope, shapeType, session_id, config, callback)`, and the published client's five-argument call lands its **callback** exactly where `config` is expected — which is precisely what the error describes. - -**The direction is the opposite of what this leaf said until 2026-09-16.** The earlier reading — a broker built before `orm_start` was renamed `orm_start_graph` (upstream `d5ecd0fd`, 2026-01-16) — is **refuted**: the name resolves, and the call fails *inside* the function, on an argument. That arity exists in **no** commit of `nextgraph-rs` at `213338f6`: the one window that carried a `config` at all (`c38aa371` 2026-04-12 → `7c8abd0a` 2026-04-21, *"rm unused config param"*) put it **last**, after the callback, where a five-argument call would leave it merely absent. The deployment is therefore built from a source **ahead of** that checkout, and it is the published `@ng-org/orm@0.1.2-alpha.19` / `@ng-org/web@0.1.2-alpha.13` this package pins that are behind it. - -**Why it fails in silence:** `orm_start_graph` is awaited inside a `try` whose `catch` is `console.error(e)`, and `resolveReady()` is called on one path only — the arrival of initial data (`GraphOrmSubscription.ts:handleInitialResponse`). A rejected start leaves the readiness promise pending for ever and the set permanently empty. This is the same upstream property already recorded in `docs/api-contract.md` (a failed read *is* an eternal pending upstream), reached here through a different door. - -## What is affected, and what is not - -- **Affected:** the published `useShape`, and only it. By the same argument, `insertObject` and `getObjects` — both built on `OrmSubscription` — cannot start either, so **no ORM write or deletion can be exercised against this deployment**. -- **Not affected:** `watchShape`, `subscribeDoc`, `subscribeDocs`, `readUnion`. These open one `doc_subscribe` per document, and `doc_subscribe: 2` has been in the method tables continuously under a stable name and arity. `watchShape` is built on that path, not on the ORM subscription — it is a working reactive read today. - -That is why the library's own suites and the reference application do not show it: nothing in this package calls `useShape` internally. - -## What to do - -- **Do not build on live `useShape`.** Use `watchShape` for a reactive read, `readUnion` for a one-shot one. This is stated in the contract's `## Non-guarantees` so a consumer meets it without reading this leaf. -- **The fix is not ours, and it is an ALIGNMENT, not an upgrade in one direction.** Either the deployment rolls to a build whose `orm_start_graph` matches the published packages, or `@ng-org/orm` / `@ng-org/web` are republished against the deployed signature. Report it; do not patch around it, and do not pass a `config` argument from here — a client that guesses an unpublished arity breaks the day the two ends agree. -- **To check whether it is closed:** `bun run e2e/orm-live-read.ts` from `packages/polyfill` (it needs `@ng-org/orm`, an optional peer `bun install` does not fetch). It exits non-zero while this is open and names what the engine expected. Green means the two ends agree and this leaf can go. - -## The shape worth remembering - -A method **signature** — name and arity together — duplicated across three independently-deployed artifacts, with a **silent** failure when they disagree, is a defect no single repository's tests can catch. Pinning the client packages does not help: the broker-side half (`@ng-org/api-web`, `private: true`, shipped only inside the broker build) is pinnable by nobody, so the two ends drift in either direction and the symptom is identical both ways. Anything this package routes through a streamed method inherits the same exposure — which is the reason `subscribeDocs` composing per-document `doc_subscribe` is worth more than it looks. diff --git a/.tasks/todo/T01-align-the-published-surface.md b/.tasks/todo/T01-align-the-published-surface.md index 6fdf13d..f367e12 100644 --- a/.tasks/todo/T01-align-the-published-surface.md +++ b/.tasks/todo/T01-align-the-published-surface.md @@ -46,3 +46,5 @@ The polyfill's published surface has drifted from the SDK's: invented names, inv - T01.a — the taxonomy gains `announced-not-written`: its `ref` points at the `.md` in THIS repo where the statement is on record, never a source file, plus `announced-by=@`. Upstream citations now resolve-check only when the sibling checkout is present; announced records always. Two latent defects fell out: the test's closed set was missing `reserved-name`, and the rule said "five kinds" over six rows. All 92 existing refs already resolved. - T01.b — deletion is answered at level 3 and fully wired: `DeepSignalSet.delete` -> a remove patch -> `graph_orm_update` -> `SparqlBuilder::remove_object`, which removes the SHAPE's predicates, not the subject; no document deletion exists at any level. `getObjects` returns a DETACHED clone; `insertObject` throws without `@graph`. Proposals: publish `OrmSubscription` as-is (the read filter applies to `signalObject`), publish NEITHER `DiscreteOrmSubscription` (JSON-CRDT, no `@graph`, nothing for a per-document filter to read) NOR `ngSession` (hands out the SDK handle and the session). MEASURED: `useShape` is still broken live, and the bug leaf's mechanism was FALSE - the deployment is AHEAD of our pinned `@ng-org/*`, its `orm_start_graph` takes `config` BEFORE the callback, so the five-argument client lands its callback in the wrong slot. T01.d's blocker STANDS. Also VERIFIED: ORM writes bypass `sparql_update` entirely, so the emulated write guard never sees an ORM add or delete. - T01.c - `useShapeQuery` takes the SDK's `Scope`, re-exported as a passthrough. Our three literals became `StoreType`, internal only: VERIFIED they are upstream's own `store_type`, so only the NAME was invented. A store is now named by its REFERENCE (`privateStore()` / `protectedStore()` / `publicStore()`), and `createEntityDoc(store?)` takes one, private when omitted, as `doc_create` does. `listMyEntityDocs`, `resolveScopeGraph` and `resolveWriteGraph` unpublished, still internal - T01.d removes the read path's last caller. VERIFIED: public and protected share one store id here, so the reference must be the index document. `1.0.0-dev.5`. +- Package refresh (2026-09-21) - `@ng-org/*` moved to web alpha.14 / orm alpha.21 / shex-orm alpha.9 / alien-deepsignals alpha.13. The live ORM read now STARTS against the deployed broker: `6 passed, 0 failed, 6 declared`, ready in 16ms, delete reached the document. `bug_useshape-live-read-needs-a-newer-broker` DELETED - the deployment was never behind, our packages were. T01.d's blocker LIFTS. Also surfaced: `OrmSubscription` is now `RdfOrmSubscription` (T01.e's proposal names the old one); `Scope.graphs` is REQUIRED and its `[]` means the EMPTY set while `subjects: []` means NO filtering - the two axes read `[]` in opposite directions, which T01.d must not get wrong; six e2e files imported `ng-e2e-helpers` without the polyfill declaring it, fixed as `workspace:*`. + diff --git a/packages/polyfill/e2e/orm-live-entry.ts b/packages/polyfill/e2e/orm-live-entry.ts index ed7b4cc..02975a3 100644 --- a/packages/polyfill/e2e/orm-live-entry.ts +++ b/packages/polyfill/e2e/orm-live-entry.ts @@ -250,19 +250,26 @@ async function seededTripleCount(doc: Nuri, subject: string): Promise { /** * CHARACTERISATION of the deployed broker's `orm_start_graph` ARITY. * - * The failure the runner reports names a parameter — `config` — that the ORM does not - * send and that `nextgraph-rs` at the read commit does not declare. Two upstream - * arrangements could put one there, and they differ by POSITION: + * The failure the runner reports named a parameter - `config` - that the client of + * the day did not send. Two upstream arrangements could put one there, and they differ + * by POSITION: * - config LAST, after the callback (upstream 2026-04-12 to 2026-04-21) — an * unsupplied `config`, never a Function; - * - config at index 4, BEFORE the callback — the 5-argument call from `@ng-org/orm` - * then lands its callback exactly there, which is what the error describes. + * - config at index 4, BEFORE the callback: the 5-argument call of the client of + * the day then lands its callback exactly there, which is what the error described. * - * So this calls the SDK member directly with an empty config in position 4. It does not - * go through the ORM, which is fixed at five arguments — this is the one question the - * ORM cannot ask. The app-side proxy will mistake the `{}` for the stream callback, so - * nothing usable comes back; what is being read is only whether the engine still - * refuses on `config`. + * THE UPDATE ANSWERED THAT QUESTION. `@ng-org/web@0.1.2-alpha.14` declares six + * arguments with `config` at index 4 and routes the stream callback from index 5 + * (`dist/ngweb.js`: `orm_start_graph: 5`), and `@ng-org/orm@0.1.2-alpha.21` sends six + * to match (`dist/connector/RdfOrmSubscription.js:159`). So the second arrangement was + * the right reading, and the ordinary `liveStart` path now exercises it. + * + * This is therefore kept as a FALLBACK, not as the live question: the runner only asks + * it when `liveStart` did not become ready, to say whether the engine still refuses on + * `config` when the client no longer can be blamed. It calls the SDK member directly + * rather than through the ORM. The app-side proxy takes index 5 as the stream callback, + * so the `{}` at index 4 is read as a config and nothing usable comes back; what is + * being read is only whether the start is refused. */ async liveStartWithConfig() { const s = await sessionReady; @@ -273,14 +280,12 @@ async function seededTripleCount(doc: Nuri, subject: string): Promise { "store", undefined, )) as Nuri; - // Six arguments against a declaration that has five: the declaration describes the - // arity of the PUBLISHED client, and the arity of the DEPLOYED engine is the question. - // Widened through `unknown`, never `any` — the call is deliberate, not unchecked. - const startGraph = realNg.orm_start_graph as unknown as ( - ...a: unknown[] - ) => Promise; + // Called against the PUBLISHED declaration, which now has these six arguments, so the + // widening this used to need is gone: the arity is checked here rather than asserted. + // What remains unknowable at compile time is the DEPLOYED engine's arity, which is + // the question this asks. try { - await startGraph([doc], [], LIVE_SHAPE, s.session_id, {}, () => {}); + await realNg.orm_start_graph([doc], [], LIVE_SHAPE, s.session_id, {}, () => {}); return { doc, error: null }; } catch (e) { return { doc, error: e instanceof Error ? e.message : String(e) }; diff --git a/packages/polyfill/e2e/orm-live-read.ts b/packages/polyfill/e2e/orm-live-read.ts index 350c100..51156cd 100644 --- a/packages/polyfill/e2e/orm-live-read.ts +++ b/packages/polyfill/e2e/orm-live-read.ts @@ -261,9 +261,11 @@ async function main(): Promise { } check(CHECKS[4]!, remaining === 0, `triples left for the seeded subject: ${remaining}`); - // Only worth asking when the five-argument call did NOT start: it is the diagnosis of + // Only worth asking when the published call did NOT start: it is the diagnosis of // that failure, and a deployment that answers the published arity has nothing to - // characterise. + // characterise. Since `@ng-org/web@0.1.2-alpha.14` the published call is itself the + // six-argument one, so this asks whether the engine refuses a start the client got + // right - no longer whether the client was sending the wrong number. if (readyMs === null) { const withConfig = await sdk<{ doc: string; error: string | null }>( frame, @@ -273,12 +275,12 @@ async function main(): Promise { CHECKS[5]!, withConfig.error === null ? "six arguments with `{}` in position 4 are ACCEPTED — the deployed " + - "`orm_start_graph` takes a `config` BEFORE the callback, so the published " + - "client's five-argument call puts its callback there" + "`orm_start_graph` takes a `config` BEFORE the callback, which is what the " + + "published client now sends, so the refusal is not an arity mismatch" : `six arguments were refused too: ${withConfig.error}`, ); } else { - record(CHECKS[5]!, "not asked — the published five-argument call started"); + record(CHECKS[5]!, "not asked — the published six-argument call started"); } } finally { if (page) await closeQuietly("the ORM live-read page", () => page!.close()); diff --git a/packages/polyfill/e2e/tsconfig.json b/packages/polyfill/e2e/tsconfig.json index b70c899..a940899 100644 --- a/packages/polyfill/e2e/tsconfig.json +++ b/packages/polyfill/e2e/tsconfig.json @@ -2,10 +2,7 @@ "extends": "../../../tsconfig.base.json", "compilerOptions": { "types": ["bun"], - "noEmit": true, - "paths": { - "@ng-org/web": ["../node_modules/@ng-org/web"] - } + "noEmit": true }, "include": ["."] } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a8d913c..a45fda2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -21,8 +21,8 @@ importers: packages/ng-e2e-helpers: devDependencies: '@ng-org/web': - specifier: 0.1.2-alpha.13 - version: 0.1.2-alpha.13 + specifier: 0.1.2-alpha.14 + version: 0.1.2-alpha.14 playwright: specifier: ^1.61.1 version: 1.61.1 @@ -87,9 +87,6 @@ packages: peerDependencies: typescript: ^4.9.0 || ^5.0.0 || ^6.0.0 - '@ng-org/web@0.1.2-alpha.13': - resolution: {integrity: sha512-/xO0c+3NTphnws5Do2LDqgZWmAf+aNnYdChJKdU0dnp1U1iVSgi/y3yb8AYryf0v9sooj0aYJxt08B6DpirFMQ==} - '@ng-org/web@0.1.2-alpha.14': resolution: {integrity: sha512-FPQ7kyngMhAXK/WJtZ6pkoEJwGcau6GaBH2nhjjYorMq7OG//I+zwghat90FsIeMrfYsps0+5Y0UrRqfxNdUXA==} @@ -514,10 +511,6 @@ snapshots: transitivePeerDependencies: - encoding - '@ng-org/web@0.1.2-alpha.13': - dependencies: - async-proxy: 0.4.1 - '@ng-org/web@0.1.2-alpha.14': dependencies: async-proxy: 0.4.1