From 0cebf6e29a1ddabf357c7246ab3024679dff59f4 Mon Sep 17 00:00:00 2001 From: Sylvain Duchesne Date: Thu, 1 Oct 2026 12:10:28 +0200 Subject: [PATCH] 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. --- .../contract_polyfill-surface.md | 12 ++++++-- docs/api-contract.md | 2 ++ packages/polyfill/package.json | 2 +- packages/polyfill/src/index.ts | 30 ++++++++++++++++++- 4 files changed, 41 insertions(+), 5 deletions(-) diff --git a/.project/concepts/app-contract/polyfill-surface/contract_polyfill-surface.md b/.project/concepts/app-contract/polyfill-surface/contract_polyfill-surface.md index d4ef073..d431836 100644 --- a/.project/concepts/app-contract/polyfill-surface/contract_polyfill-surface.md +++ b/.project/concepts/app-contract/polyfill-surface/contract_polyfill-surface.md @@ -149,6 +149,10 @@ export function init(...args: any[]): any; // likewise — not the `init` p 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 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). - **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. diff --git a/docs/api-contract.md b/docs/api-contract.md index bd47c0a..417e6a4 100644 --- a/docs/api-contract.md +++ b/docs/api-contract.md @@ -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`. +**`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**: - **`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. diff --git a/packages/polyfill/package.json b/packages/polyfill/package.json index a263647..7e94ee8 100644 --- a/packages/polyfill/package.json +++ b/packages/polyfill/package.json @@ -1,6 +1,6 @@ { "name": "@ng-eventually/polyfill", - "version": "1.0.0-dev.13", + "version": "1.0.0-dev.14", "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.", "main": "./src/index.ts", diff --git a/packages/polyfill/src/index.ts b/packages/polyfill/src/index.ts index a635cf6..2826a42 100644 --- a/packages/polyfill/src/index.ts +++ b/packages/polyfill/src/index.ts @@ -118,7 +118,9 @@ export type { Nuri, NuriLike, PrincipalId } from "./model/types"; export { useShape } from "./surface/use-shape"; 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 // 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 @@ -195,6 +197,32 @@ export type { GetObjectsLike, InsertObjectLike, } 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 // 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