fix: ShapeObservable rejoint la liste de suppression et le contrat dit le trou de refetch

ShapeObservable portait l'annotation honnete kind=invention level=none ref=none
mais siegeait sous l'entete SDK-SHAPED qui promet un equivalent cible pour chaque
symbole. Il passe sous la barriere POLYFILL-ERA, toujours exporte (des
applications l'ecrivent : examples/notebook, e2e/polyfill-entry.ts). ShapeQuery
reste SDK-SHAPED, son enregistrement existe.

Ce n'est pas un adaptateur de framework : il porte le filtre de capacites et la
republication a l'arrivee d'une capacite, deux emulations du portefeuille
partage. Il disparait donc avec lui, migration couplee et non suppression
independante.

Le contrat enonce aussi que refetch() n'a aucun equivalent enregistre : les cinq
champs annonces ne le comptent pas. C'est un fait sur l'enregistrement, pas sur
TanStack ni sur les intentions de NextGraph.

PATCH, par le precedent du 2026-09-28 : aucune signature ne change. Version
1.0.0-dev.13 -> 1.0.0-dev.14, entree dans le contrat concept et dans
docs/api-contract.md section 14, dans le meme commit.
This commit is contained in:
Sylvain Duchesne
2026-10-01 12:10:28 +02:00
parent acdaf8b246
commit 0cebf6e29a
4 changed files with 41 additions and 5 deletions
@@ -149,6 +149,10 @@ export function init(...args: any[]): any; // likewise — not the `init` p
export function initNg(...args: any[]): any; export function initNg(...args: any[]): any;
``` ```
**`ShapeObservable` is on the deletion list, and it goes WITH the shared wallet, not before it.** It is not a framework adapter: it carries the capability filter (`getSnapshot()` returns a view filtered by the capabilities held) and the capability-arrival re-publish (a subscription that re-publishes when a capability arrives). A capability arrives mid-session only because the wallet is shared; in the target an identity holds its own wallet and its capabilities from the start, so there is nothing to filter and no arrival to re-publish on. The announced form, a hook returning `{ data, isPending, isSuccess, isError, error }`, has nowhere to hang such a subscription, because the target does not need one. The type stays exported and you may write it down; what you cannot do is plan to drop it on its own ahead of the rest of the shared-wallet code.
**`refetch()` has NO RECORDED COUNTERPART.** It is the only published way out of a read that is stuck pending. The announcement recorded in this repository lists five fields, `{ data, isPending, isSuccess, isError, error }`, and `refetch` is not among them. That is a statement about the RECORD, nothing more: TanStack Query's own `useQuery` does return a `refetch`, so a successor may well exist, but this repository holds no record of one and cannot name what an application leaning on `refetch()` would migrate onto. Treat it as an open migration point, not as a call that has a mapped replacement.
## Guarantees ## Guarantees
Every entry accepts `NuriLike` and validates at the door; what it returns is a precise `Nuri`. No type guard is published. Every entry accepts `NuriLike` and validates at the door; what it returns is a precise `Nuri`. No type guard is published.
@@ -283,11 +287,13 @@ What each level means here, in this package's own terms:
- **minor** — a symbol is added and nothing existing moves (`inbox.readSyncedForDocument` arrived this way). - **minor** — a symbol is added and nothing existing moves (`inbox.readSyncedForDocument` arrived this way).
- **patch** — a fix that changes neither the published surface nor anything above under `## Guarantees`. - **patch** — a fix that changes neither the published surface nor anything above under `## Guarantees`.
**A tag says where it comes from.** A release cut on `main` carries a **full version** (`1.0.0`), and the three rules above govern what changes between two full versions. Work still on a branch carries a **pre-release** of the version it is heading for (`1.0.0-dev.13`), which sorts *below* that version by construction - so you can pin what exists today while the tag itself tells you the surface has not been released and may still move before it is. Between two pre-releases of the same version nothing is promised: re-pull and read this leaf again. When the branch lands, the full version appears alongside; the pre-release keeps resolving, so no reference you pinned is ever withdrawn from under you. **A tag says where it comes from.** A release cut on `main` carries a **full version** (`1.0.0`), and the three rules above govern what changes between two full versions. Work still on a branch carries a **pre-release** of the version it is heading for (`1.0.0-dev.14`), which sorts *below* that version by construction - so you can pin what exists today while the tag itself tells you the surface has not been released and may still move before it is. Between two pre-releases of the same version nothing is promised: re-pull and read this leaf again. When the branch lands, the full version appears alongside; the pre-release keeps resolving, so no reference you pinned is ever withdrawn from under you.
**Tags carry the package name**, because this repository publishes more than one engagement and their versions move independently: `polyfill/v1.0.0-dev.13` is this package, `ng-e2e-helpers/v...` is the other one. A bare `v...` tag would say nothing about which surface it froze the day the two diverge - which is the day one of them takes a major and the other does not. **Tags carry the package name**, because this repository publishes more than one engagement and their versions move independently: `polyfill/v1.0.0-dev.14` is this package, `ng-e2e-helpers/v...` is the other one. A bare `v...` tag would say nothing about which surface it froze the day the two diverge - which is the day one of them takes a major and the other does not.
`1.0.0` is a baseline, not a claim of maturity: it is the number that makes your pin mean something. Nothing was released before it, so none of the changes named above is a bump from anything - but the next release very likely is a major. What exists today is `1.0.0-dev.13`, on a branch: pin that string exactly, and anchor your `usage_` leaf's `against:` on it - `against: @ng-eventually/polyfill@1.0.0-dev.13`, the string you pinned, never the version it is heading for. `1.0.0` is a baseline, not a claim of maturity: it is the number that makes your pin mean something. Nothing was released before it, so none of the changes named above is a bump from anything - but the next release very likely is a major. What exists today is `1.0.0-dev.14`, on a branch: pin that string exactly, and anchor your `usage_` leaf's `against:` on it - `against: @ng-eventually/polyfill@1.0.0-dev.14`, the string you pinned, never the version it is heading for.
`1.0.0-dev.14` is a **patch** (2026-10-01): `ShapeObservable` moved from the entry's SDK-SHAPED section to its POLYFILL-ERA one, and `## Surface` now states that `refetch()` has no recorded counterpart. No signature changed - same name, same shape, still exported from this one entry - so nothing under `## Guarantees` moved. The type was already annotated `kind=invention level=none ref=none` (no upstream observable exists), under a heading that promises a target counterpart for every symbol it lists. It stays exported because applications genuinely write the type down. What the move records is that it is not a framework adapter: it carries the capability filter and the capability-arrival re-publish, both of which exist only because one wallet is shared, so it is deleted when the shared wallet is, as one coupled migration. `ShapeQuery` stays SDK-SHAPED: its record exists and states `{ data, isPending, isSuccess, isError, error }` field for field.
`1.0.0-dev.13` is a **patch**: `NgLike`, `UseShapeLike`, `OrmSubscriptionLike`, `GetObjectsLike` and `InsertObjectLike` moved from the entry's SDK-SHAPED section to its POLYFILL-ERA one, beside `EventuallyConfig`, whose fields they type. No signature changed - same five names, same five shapes, all still exported from this one entry - so nothing under `## Surface` or `## Guarantees` moved, which is the patch/major line this leaf draws itself. What changed is classification only: each was already honestly annotated `kind=invention level=none ref=none` (no target counterpart, at any level), sitting under a heading that promises one for every symbol it lists, because each exists solely to describe what `configure` receives and `configure` was already on the deletion list. An application that already understood it would stop typing these five the day it deletes its `configure()` call was not misled about behaviour; this patch only makes the surface say so, instead of implying the opposite. `1.0.0-dev.13` is a **patch**: `NgLike`, `UseShapeLike`, `OrmSubscriptionLike`, `GetObjectsLike` and `InsertObjectLike` moved from the entry's SDK-SHAPED section to its POLYFILL-ERA one, beside `EventuallyConfig`, whose fields they type. No signature changed - same five names, same five shapes, all still exported from this one entry - so nothing under `## Surface` or `## Guarantees` moved, which is the patch/major line this leaf draws itself. What changed is classification only: each was already honestly annotated `kind=invention level=none ref=none` (no target counterpart, at any level), sitting under a heading that promises one for every symbol it lists, because each exists solely to describe what `configure` receives and `configure` was already on the deletion list. An application that already understood it would stop typing these five the day it deletes its `configure()` call was not misled about behaviour; this patch only makes the surface say so, instead of implying the opposite.
+2
View File
@@ -794,6 +794,8 @@ Each one's warrant: `Nuri` is what every reference-returning call returns and `N
**These five moved out of this list in `1.0.0-dev.13` (2026-09-28), into the entry's POLYFILL-ERA block beside `EventuallyConfig` - a PATCH, not a removal: same five names, same five shapes, still exported from this one entry.** Each was already annotated `kind=invention level=none ref=none` (honestly - none has a target counterpart at any level), which had always sat oddly under the entry's SDK-SHAPED heading ("a target counterpart for every symbol"). All five exist only to describe what `configure` receives, and `configure` / `EventuallyConfig` were already on the deletion list (§ 1: "NO COUNTERPART, by design... `configure` / `configureStoreRegistry` are deleted"). The move corrects which list tells a consumer their fate at migration; it changes nothing under `## Surface` or `## Guarantees` in the concept contract, so it is a patch by that leaf's own `## Change policy`. **These five moved out of this list in `1.0.0-dev.13` (2026-09-28), into the entry's POLYFILL-ERA block beside `EventuallyConfig` - a PATCH, not a removal: same five names, same five shapes, still exported from this one entry.** Each was already annotated `kind=invention level=none ref=none` (honestly - none has a target counterpart at any level), which had always sat oddly under the entry's SDK-SHAPED heading ("a target counterpart for every symbol"). All five exist only to describe what `configure` receives, and `configure` / `EventuallyConfig` were already on the deletion list (§ 1: "NO COUNTERPART, by design... `configure` / `configureStoreRegistry` are deleted"). The move corrects which list tells a consumer their fate at migration; it changes nothing under `## Surface` or `## Guarantees` in the concept contract, so it is a patch by that leaf's own `## Change policy`.
**`ShapeObservable` moved the same way in `1.0.0-dev.14` (2026-10-01) - a PATCH again: same name, same shape, still exported.** It is annotated `kind=invention level=none ref=none` (no upstream observable exists; the record behind `ShapeQuery` describes a snapshot shape, not an observable), so it sat wrongly under the SDK-SHAPED heading. `ShapeQuery` stays there: its record exists and states `{ data, isPending, isSuccess, isError, error }` field for field. The type stays exported because applications write it down (`examples/notebook/app.ts`, `e2e/polyfill-entry.ts`). It is not a framework adapter: it carries the capability filter (`getSnapshot()` returns a filtered view) and the capability-arrival re-publish (a `getCaps().onChange` subscription owned by the observable's own lifecycle). A capability arrives mid-session only because the wallet is shared; in the target an identity holds its own wallet and capabilities from the start, so there is nothing to filter and no arrival to re-publish on. It therefore dies exactly when the shared wallet dies - a COUPLED migration, not an independent symbol removal - and a TanStack-shaped hook returning a snapshot has nowhere to hang such a subscription, because the target does not need one. `refetch()` lives on it and is the only published way out of a read stuck pending; the recorded announcement lists five fields and `refetch` is not among them, so it goes with NO RECORDED COUNTERPART. That is a statement about the record, not about TanStack Query (whose `useQuery` does return a `refetch`) nor about NextGraph's intentions.
Two types the blanket export published are now internal, each because **nothing published names it**: Two types the blanket export published are now internal, each because **nothing published names it**:
- **`ReadCap`** — no published signature takes or returns one. Its users are two private helpers of `surface/inbox.ts` (`capsSeenIn`, `capOfPayload`) and the emulated registers. Publishing it advertised a value a consumer has no published call to obtain, and deliberately so: `linkTo` was removed precisely for handing one out (§ 0 of `readcap-and-nuri-model.md`), and `mintCap` is unreachable from outside (§ 11). The only use a consumer could make of it is a cast — which is what the surface's permissive-in / precise-out design exists to make unnecessary. - **`ReadCap`** — no published signature takes or returns one. Its users are two private helpers of `surface/inbox.ts` (`capsSeenIn`, `capOfPayload`) and the emulated registers. Publishing it advertised a value a consumer has no published call to obtain, and deliberately so: `linkTo` was removed precisely for handing one out (§ 0 of `readcap-and-nuri-model.md`), and `mintCap` is unreachable from outside (§ 11). The only use a consumer could make of it is a cast — which is what the surface's permissive-in / precise-out design exists to make unnecessary.
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@ng-eventually/polyfill", "name": "@ng-eventually/polyfill",
"version": "1.0.0-dev.13", "version": "1.0.0-dev.14",
"type": "module", "type": "module",
"description": "Polyfill of the NextGraph JS SDK over @ng-org/web + @ng-org/orm, with emulated capabilities and inbox. Drop-in; remove at migration.", "description": "Polyfill of the NextGraph JS SDK over @ng-org/web + @ng-org/orm, with emulated capabilities and inbox. Drop-in; remove at migration.",
"main": "./src/index.ts", "main": "./src/index.ts",
+29 -1
View File
@@ -118,7 +118,9 @@
export type { Nuri, NuriLike, PrincipalId } from "./model/types"; export type { Nuri, NuriLike, PrincipalId } from "./model/types";
export { useShape } from "./surface/use-shape"; export { useShape } from "./surface/use-shape";
export { useShapeQuery } from "./surface/use-shape-query"; export { useShapeQuery } from "./surface/use-shape-query";
export type { ShapeQuery, ShapeObservable } from "./surface/use-shape-query"; export type { ShapeQuery } from "./surface/use-shape-query";
// `ShapeObservable` is exported from the POLYFILL-ERA block below, beside `configure`:
// it is `kind=invention level=none ref=none` and carries shared-wallet emulation.
// The ORM's one-shot read and its subscription-free write - WRAPPED, at upstream's own // The ORM's one-shot read and its subscription-free write - WRAPPED, at upstream's own
// names and signatures, never re-exported. `surface/orm.ts` says what each wrapper adds, // names and signatures, never re-exported. `surface/orm.ts` says what each wrapper adds,
// and which neighbours of theirs in `@ng-org/orm`'s entry are deliberately not here // and which neighbours of theirs in `@ng-org/orm`'s entry are deliberately not here
@@ -195,6 +197,32 @@ export type {
GetObjectsLike, GetObjectsLike,
InsertObjectLike, InsertObjectLike,
} from "./model/types"; } from "./model/types";
// `ShapeObservable` - what `useShapeQuery` returns - moved down here on 2026-10-01. It is
// `kind=invention level=none ref=none` (no upstream observable exists; the record behind
// `ShapeQuery` describes a SNAPSHOT shape, not an observable), so it never belonged under
// the SDK-SHAPED heading. It stays EXPORTED: applications genuinely write the type down
// (`examples/notebook/app.ts` holds one in a variable, returns it and takes it as a
// parameter; `e2e/polyfill-entry.ts` annotates it). Only the documenting section changes.
//
// It is NOT a framework adapter. It carries two pieces of shared-wallet emulation:
// - the CAPABILITY FILTER - `getSnapshot()` returns a view filtered by the capabilities
// held (`use-shape-query.ts`, `filterReadable` under `caps.isEnforcing()`);
// - the CAPABILITY-ARRIVAL RE-PUBLISH - a `getCaps().onChange(...)` subscription owned
// by the observable's own lifecycle and released with it.
// A capability arrives MID-SESSION only because the wallet is shared. In the target an
// identity holds its own wallet and its capabilities from the start: nothing to filter,
// no arrival to re-publish on. So `ShapeObservable` dies exactly WHEN THE SHARED WALLET
// DIES - a COUPLED migration with the shared wallet, not an independent symbol removal.
// Deleting the type and "using the hook" is not a step that stands alone.
//
// The announced form cannot carry what ours carries: a TanStack-shaped hook returning a
// snapshot has nowhere to hang a capability-arrival subscription, because the target
// does not need one.
//
// `refetch()` lives on it, and is the ONLY published way out of a read stuck pending.
// The recorded announcement lists five fields, `{ data, isPending, isSuccess, isError,
// error }`, and `refetch` is not among them: it goes with NO RECORDED COUNTERPART.
export type { ShapeObservable } from "./surface/use-shape-query";
// `RegistrySession` went with `getSession` (2026-08-12): it was published for exactly one // `RegistrySession` went with `getSession` (2026-08-12): it was published for exactly one
// reason — an application typed the session thunk it injected with it — and a published // reason — an application typed the session thunk it injected with it — and a published
// type whose signature is gone is a promise about the target that nothing keeps. Upstream a // type whose signature is gone is a promise about the target that nothing keeps. Upstream a