Compare commits
2 Commits
dcde2859fe
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 259d7fe58d | |||
| 6875d8b353 |
@@ -2,7 +2,7 @@
|
||||
type: overview
|
||||
summary: How a Refine application's resources are bound to NextGraph documents through the eventual SDK — and what that binding may rely on
|
||||
triggers:
|
||||
keywords: [refine, provider, dataProvider, authProvider, liveProvider, resource, getList, getOne, shapeTypes, shape, scope, store, polyfill, eventually]
|
||||
keywords: [refine, provider, dataProvider, authProvider, liveProvider, accessControlProvider, resource, getList, getOne, shapeTypes, shape, scope, store, polyfill, eventually, activitypods, interchangeable, equivalence]
|
||||
paths:
|
||||
- "src/**"
|
||||
- "package.json"
|
||||
@@ -25,3 +25,4 @@ The distinguishing rule: **nothing here compensates for the current state of Nex
|
||||
|
||||
- `polyfill-surface/contract_polyfill-surface` — the engagement this package consumes (pulled, version-pinned).
|
||||
- `polyfill-surface/usage_refine-providers-eventually` — what this package actually relies on, and what it still needs.
|
||||
- `brief_2026-10-01_interchangeability-with-activitypods` — the bar this binding is held to, and the questions still open on it.
|
||||
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
---
|
||||
type: brief
|
||||
summary: What "interchangeable with @activitypods/refine-providers" has to mean for this binding — what the adapter owes, what waits on the polyfill, and what has no analogue at all
|
||||
---
|
||||
|
||||
# Interchangeability with `@activitypods/refine-providers`
|
||||
|
||||
## The bar
|
||||
|
||||
This package is held to being **at least as capable as `@activitypods/refine-providers`**, because the two sets of providers must be interchangeable: an application should move from one to the other without rewriting its pages. That reference (v0.1.5, same author, a structural twin of `@ng-org/refine-providers`) is therefore the specification of what must work — not the `DataProvider` interface in the abstract, and not the NextGraph sibling, which is thinner than both.
|
||||
|
||||
Interchangeability is claimed on **what an application can call and render**. Each binding gets its authentication from what it sits on — the reference from a Pod provider, this one from the engagement it consumes — and how far either goes is that provider's business, not this package's. Nothing about either mechanism belongs in this repository: a binding that reasoned about its upstream's internals would hold knowledge it cannot verify, that drifts when the upstream moves, and that its own consumers would have to unlearn.
|
||||
|
||||
## What the adapter owes, and can deliver alone
|
||||
|
||||
The decisive finding of the comparison: the reference asks its server for almost none of this. Containers support no server-side filtering, sorting or pagination, so it fetches the whole container and computes in memory. That is the same posture available here, which moves most of the gap off the polyfill and onto this package.
|
||||
|
||||
| | the reference | here |
|
||||
|---|---|---|
|
||||
| Filters | every Refine operator, plus nested `and`/`or`, in memory, case-insensitive by default with sensitive variants | the same, over the read result — the NextGraph sibling honours `eq` only and warns on the rest |
|
||||
| Sorting, pagination, `total` | in memory, over the whole container | the same, over the whole shape read |
|
||||
| `getMany` | one read per id, `allSettled`, keeps what answered | one read per document — which also sidesteps the all-or-nothing of naming several documents at once |
|
||||
| Live | one subscription per record (`Update`/`Delete`), one on the container (`Add`/`Remove`); **list views never see a member's own fields change**, a limit it documents | one subscription per document, over the documents a read hands back — `subscribeDoc` is the guaranteed channel. A diff of shape-query snapshots would be the elegant route and must NOT be assumed: the engagement promises a re-publication when a **capability arrives**, and nowhere when a held document's **content** changes |
|
||||
|
||||
## What waits on the polyfill
|
||||
|
||||
`update` and `deleteOne`. The reference serves both as ordinary awaited calls — a read-then-merge `PUT`, and a `DELETE`. Here no published call is both shape-typed and awaitable: removal goes through the live set a shape query hands back, reachable only from a React hook, and a mutation of that set is batched into a call nothing awaits. Refine requires a promise that resolves with the record or rejects.
|
||||
|
||||
This is the one gap with no caller-side answer, and it is declared in `polyfill-surface/usage_refine-providers-eventually.md`, with the ownership predicate an edit control would need to know whether to show itself.
|
||||
|
||||
**And deletion is deeper than a missing call.** The engagement publishes no removal of any kind, and describes what a removal through the live set does take away: the predicates the shape declares, while the subject, the document and every key already handed out survive. So on this surface `deleteOne` cannot make a record absent the way the reference's `DELETE` does — a bookmarked show page renders an empty record instead of a missing one. Calling this "a missing call" understates it; what it needs is a published meaning for deletion, which is question 5 below.
|
||||
|
||||
## What has no analogue
|
||||
|
||||
The reference's auth provider is not only authentication. It carries **delegated access consent**: an application registers itself against the person's Pod, declares the access it needs, and is sent to an authorization agent for approval — `registerApp()`, `getAppStatus()`, `upgradeNeeded`, a periodic re-check, and two antd components (`AntdAuthPage`, `AntdBackgroundChecks`).
|
||||
|
||||
The engagement publishes nothing of the kind, and places user management outside its `## Scope` — so there is no surface here to bind such a flow to, and none to be asked for: what the eventual SDK will do about application consent is not something this leaf can know, and guessing it is how a binding ends up teaching a model that never arrives. The question is therefore what THIS package answers when an application calls those two methods, not what the platform ought to provide.
|
||||
|
||||
**`logout` DOES have an analogue — what is missing is the call.** This is worth stating carefully, because getting it wrong is how a binding teaches a model that will not exist. In the target each person opens their own wallet, so an open wallet IS the authenticated state, and closing it is the sign-out; `ensureIdentity()` is the emulation of the first act, and this binding must be wired to it as such — `check()` asks whether an identity is settled, `getIdentity()` asks who, `logout()` closes. That shape survives migration unchanged.
|
||||
|
||||
What does not exist yet is any published way to perform the closing: the engagement names "changing identity or clearing it" as an event that occurs, and offers no call that causes it. So an application that switches bindings keeps its Logout button and it stops protecting anything — the barrier comes back prefilled, one click re-enters the identity that just left, and `ensureIdentity()` shows no barrier at all. That is a NEED (declared), not an absence of analogue, and until it is served this binding's `logout` can only clear what it holds itself and say so.
|
||||
|
||||
## Open questions
|
||||
|
||||
~~1. What does "interchangeable" cover?~~ **SETTLED (2026-10-01): the pages and the providers' surface — not the antd components.**
|
||||
|
||||
The three providers expose the same methods as the reference, `registerApp`, `getAppStatus` and `handleCallback` included, in a degenerate behaviour: there is nothing to register, so registering resolves at once and the status reads installed and up to date. Each binding keeps its own bootstrap — this one needs `configure` and an awaited `ensureIdentity()` before the interface renders — and no antd component is published here: the polyfill's own barrier leaves a login page almost nothing to do, and a gate component nothing to gate.
|
||||
|
||||
**What decided it was the reverse direction.** "Interchangeable" is symmetric, and an application written here and later moved to ActivityPods must have registered itself to read anything there. Without the method it never called it, and the failure lands in the other binding, silently, caused by a surface missing from this one. Exposing it means an application calls it by construction.
|
||||
|
||||
**How the degenerate answer must be worded.** "Nothing to register" describes THIS package: the engagement publishes no application registration, so there is nothing here to register. It says nothing about the platform, and nothing about what the eventual SDK will do with application consent. The engagement this package publishes will present these three as **compatibility, never capability** — a published member that describes no mechanism is honest only if it says so.
|
||||
~~2. Where does a display name come from?~~ **SETTLED (2026-10-01): raised upstream; the identifier stands in meanwhile.**
|
||||
|
||||
The sibling binding offered no lead — its `getIdentity` returns a hard-coded name and a stock photo, so the question was never faced there. Inventing a profile shape here would be a convention of this package's own, which an application would have to unlearn the day the SDK publishes one, so it is declared as a need instead (an ask to widen a scope that excludes display names, justified by a caller having no conforming option: the identifier may not be rendered as a name, and no name is offered). Until it is answered, `getIdentity` returns the identifier in the `name` field — the same stand-in the reference falls back to when a profile cannot be read.
|
||||
~~3. How is the authentication difference stated?~~ **SETTLED (2026-10-01): nowhere — the question was wrongly posed.**
|
||||
|
||||
It assumed this package has something to say about how strongly its upstream authenticates. It does not: it receives an authentication and wires Refine to it. How that authentication is established, and what it is worth today, belongs to the engagement and to its own `## Non-guarantees`, where a consumer of this package reads it directly. Restating it here would duplicate a claim this package cannot verify, would drift the moment the upstream moves, and would teach an application a distinction that disappears at migration. The point stands for the whole repository, not just this leaf: **what is internal to the emulation must not be known here.**
|
||||
~~4. Does `check()` honour `allowAnonymous`?~~ **SETTLED (2026-10-01): the option is not published; `check()` fails closed.**
|
||||
|
||||
It follows from question 1 rather than needing its own arbitration: `allowAnonymous` is a field of the reference's `AuthProviderConfig`, and the bootstrap is the part each binding keeps for itself — `clientId`, `redirectUri` and `appStatusCheckInterval` have no object here either. What belongs to the interchangeable surface is the behaviour of `check()`, and there is no anonymous state for it to allow: an identity is settled before the interface renders, or the call that settles it rejected. So `check()` answers unauthenticated and redirects to the login route when no identity is settled — the reference's own default when the option is absent.
|
||||
|
||||
~~5. What should a listing contain?~~ **SETTLED (2026-10-01): everything accessible of that shape, and this package filters nothing.**
|
||||
|
||||
A listing is `getObjects(shapeType, { graphs: "" })` and that is the whole of it. The surface already returns only what the identity holds the key for, so the filtering a Refine resource needs is the one already performed on the way out — there is nothing for this package to add, decide or restrict, and it must not invent a narrower notion of "mine" on top of a surface that already answers the question.
|
||||
|
||||
Two consequences follow, both accepted rather than worked around:
|
||||
|
||||
- **A listing mixes records that can be written with records that can only be read.** The default path is therefore to let the person act and surface the refusal, exactly as any failed mutation surfaces — which needs the awaitable write, not a predicate. The ownership predicate stays requested, demoted from "required" to a comfort: it lets an interface hide a control instead of letting it fail, and it blocks nothing.
|
||||
- ~~6. Does browsing grow a resource?~~ **Yes, and that is the intended behaviour.** Opening a public document by its `reference` puts its key in what the identity holds, so it joins later listings of that shape. A listing reflects what is accessible; it is not an inventory this package curates.
|
||||
|
||||
## To graduate
|
||||
|
||||
**Every question is settled.** This brief is ready to graduate: the inventory becomes the engagement's `## Scope` and `## Non-guarantees`, the six settled answers become entries there (question 1's three compatibility members, question 5's definition of a listing, question 4's `check()`), and the brief is deleted once they are written. The engagement itself still waits on what the declaration asks of the polyfill — an engagement promises only what it can hold. Then this brief splits — the inventory above becomes the engagement's `## Scope` and `## Non-guarantees`, the settled question 1 and the answer to 3 become entries there, and what remains of 2 and 4 becomes either a `decision_` or an engagement entry.
|
||||
@@ -2,7 +2,7 @@
|
||||
type: contract
|
||||
summary: The API @ng-eventually/polyfill exposes to an application — signatures, guaranteed behaviour, and what it does not offer
|
||||
pulled_from: git@gitea.reconnexion.apps.gueraud.net:Reconnexion/ng-eventually.git/.project/concepts/app-contract/polyfill-surface/contract_polyfill-surface.md
|
||||
pulled_version: 4c1801f6ee1dba0ce193e693960a6788686d602e
|
||||
pulled_version: ec3900077bfd090f5388e52b47a8132b2fc68d16
|
||||
pulled_at: 2026-10-01
|
||||
---
|
||||
|
||||
@@ -149,9 +149,56 @@ export const ng: NG; // call this instead of the `ng` passed
|
||||
// `NG` is upstream's own type (`@ng-org/web`), fully typed
|
||||
// members; it was `Record<string, any>` until 2026-08-14
|
||||
export function init(...args: any[]): any; // likewise — not the `init` passed to `configure`
|
||||
export function initNg(...args: any[]): any;
|
||||
export function initNg(...args: any[]): any; // likewise, and position 0 is DISCARDED - see below
|
||||
```
|
||||
|
||||
**A shape, in full, because one has to be written by hand.** `ShapeType` asks for `readablePredicate`, `minCardinality` and `maxCardinality` on every predicate, a `schema` keyed by the shape's own IRI, and objects carrying `@id` and `@graph` - and assembling that from the SDK's type declarations is a read this leaf exists to spare you. The shape below is `examples/notebook/app.ts`'s, predicate for predicate - that application reads with it against a deployed broker - and `rdf-orm build` generates exactly this object from a `.shex` file, so nothing here depends on the generator. The object interface and the type argument beside it are not that application's: it reads an untyped snapshot and narrows at the call site, which works and types nothing.
|
||||
|
||||
```ts
|
||||
import { useShapeQuery } from "@ng-eventually/polyfill";
|
||||
import type { BaseType, ShapeType } from "@ng-eventually/polyfill";
|
||||
|
||||
const RDF_TYPE = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type";
|
||||
|
||||
// What a read HANDS BACK: `@id` and `@graph` come from `BaseType`, plus one field per
|
||||
// predicate, named by that predicate's `readablePredicate`.
|
||||
interface NoteObject extends BaseType {
|
||||
type: string;
|
||||
title: string;
|
||||
body: string;
|
||||
}
|
||||
|
||||
const NoteShape = {
|
||||
shape: "urn:notebook:NoteShape", // the shape's own IRI...
|
||||
schema: {
|
||||
"urn:notebook:NoteShape": { // ...which is also the `schema` key...
|
||||
iri: "urn:notebook:NoteShape", // ...and the shape's own `iri`
|
||||
predicates: [
|
||||
// The matching constraint: a subject carrying `rdf:type urn:notebook:Note`.
|
||||
{ iri: RDF_TYPE, readablePredicate: "type",
|
||||
dataTypes: [{ valType: "iri", literals: ["urn:notebook:Note"] }],
|
||||
minCardinality: 1, maxCardinality: 1 },
|
||||
// And the fields that come BACK. A predicate the shape does not NAME is not
|
||||
// projected, so a shape written only to match returns objects with no fields.
|
||||
{ iri: "urn:notebook:title", readablePredicate: "title",
|
||||
dataTypes: [{ valType: "string" }], minCardinality: 1, maxCardinality: 1 },
|
||||
{ iri: "urn:notebook:body", readablePredicate: "body",
|
||||
dataTypes: [{ valType: "string" }], minCardinality: 1, maxCardinality: 1 },
|
||||
],
|
||||
},
|
||||
},
|
||||
} satisfies ShapeType<NoteObject>;
|
||||
|
||||
// Pass the object type as the TYPE ARGUMENT. `ShapeType<T>` mentions `T` in none of its
|
||||
// members (it is `{ schema, shape }`), so nothing infers it from the shape: left out, `T`
|
||||
// is `BaseType` and the snapshot is untyped. MEASURED - `tsc` refuses the assignment.
|
||||
const notes = useShapeQuery<NoteObject>(NoteShape, { graphs: "did:ng:i" });
|
||||
```
|
||||
|
||||
`minCardinality: 1` is what turns a predicate into a required triple pattern, so a shape whose every predicate is optional matches every subject in reach. `maxCardinality: -1` is unbounded. `valType` is one of `"number" | "string" | "boolean" | "iri" | "shape"`, and `literals` pins the required value - which is how the `rdf:type` entry above becomes the match rather than a field you read.
|
||||
|
||||
**`initNg`'s FIRST ARGUMENT IS DISCARDED, and what goes in its place is the `ng` above.** The Deployment requirements say to call `initNg(ng, session)` while `## Surface` says to use this package's `ng` rather than the one `configure` received, so there is a choice to make - and it makes none: this call drops whatever sits in position 0 and hands NextGraph's ORM the published `ng`, the same object, then passes every remaining argument and the return value straight through. MEASURED on `1.0.0-dev.14`: a sentinel object passed in position 0 never arrives, and what does arrive is `===` the exported `ng`. That is deliberate rather than a defect - the ORM reaches the engine through whatever this call resolves, an ORM write leaves by `ng.graph_orm_update`, and that is where the write guard sits - but it is not something to have to establish by experiment. The parameter stays in the signature because upstream's `initNg(ngImpl, session)` has it, and a call site written for the target must keep compiling.
|
||||
|
||||
**`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.
|
||||
@@ -168,6 +215,10 @@ What was shared with you becomes readable after `ensureIdentity()`.
|
||||
|
||||
**An anchored `docs.sparqlQuery` reads exactly the document you name, and returns its triples as written.** The anchor makes that document's graph the query's default graph, so a body with no `GRAPH <...>` wrapper reads that document and nothing else; a body that DOES carry one iterates the named graphs regardless of the anchor, so do not write one. Subjects come back as the document carries them — several subjects in one document is allowed and each stays its own, since a subject may be any IRI. Reading several documents is this call once per document; there is no call that takes a list, and composing one with an anchorless query instead would silently widen what the read reaches. Recommended placement is one document per business entity: access is granted per document.
|
||||
|
||||
**And a document it cannot read is refused OUT LOUD, never swallowed into an empty answer.** A document you hold no capability for - and whose public store has none to give - is refused before the read leaves, naming the document; a public store that could not be ASKED rejects with that failure instead, because both stop the read and only one of them tells you to try again. Nothing is skipped on your behalf: tolerating one unreadable document was the removed `readUnion`'s own policy, and it went with it in `1.0.0-dev.8`, so whether one ends a loop over several is now your decision at your call site. A rejection means "unknown", never "absent", here as everywhere.
|
||||
|
||||
**Which reads reach a document in a public store from nothing but its `reference`.** This is the stranger's case - the document was shared with nobody, its `reference` circulated, and the reader holds no key for it - and two calls serve it. An anchored `docs.sparqlQuery` asks the public store for the key before it reads, and already did so before `1.0.0-dev.6`: the removed `readUnion` was that same ask, once per document, so a one-shot `document read` of one named public document is exactly what it always was. `useShapeQuery` asks too, since `1.0.0-dev.9`, once per document NAMED in `scope.graphs` - so a `shape read` serves the same case now, reactively and typed, and a holder of nothing opens a public document named that way (measured in the unit suite, and live against the broker). The three that do NOT ask, and in which such a document therefore does not appear: `useShape`, `getObjects`, and `useShapeQuery` under `graphs: "did:ng:i"` or `""` - naming no document, there is nobody to ask about. Read it once by naming it and the key lands in what you hold, which all three re-read on.
|
||||
|
||||
`urn:ng-eventually:` is reserved, and the reservation has two faces. In a document: this package writes its own registers under subjects carrying that prefix, in the same graph as your data — so **an anchored `docs.sparqlQuery` returns them alongside your triples, and it is yours to ignore them**. Until `1.0.0-dev.8` the removed `readUnion` dropped them for you; nothing does now. This is not a gain of rights: the two registers an ordinary document carries are a document's inbox address and, for a public document, the read key its store already serves to whoever asks. Nothing else of this package's is written into your documents. Do not write under that prefix yourself. In a deposit: two payload shapes are this package's own and it acts on them — a payload that IS a reference (a bare `did:ng:…` string and nothing else), and an object whose `kind` property is a string starting with `urn:ng-eventually:inbox:`. **Every other payload is yours, and is delivered to you untouched.**
|
||||
|
||||
**Do not use either shape to carry your own data.** Depositing one is still accepted and it is still returned to you by `inbox.read` and its variants — nothing is withheld — but this package now also acts on it, and what you observe is one of two things: it writes an entry into the document the inbox belongs to, if that document is one it can index; otherwise it reports a refusal on its own log stream (`console.error`, ungated, as below). That refusal is reported ONCE and then the deposit is gone: this package consumes what it claims, whether or not the arm succeeded. Until `1.0.0-dev.4` the same refusal came back at every arrival on that inbox and at every connection, for as long as the deposit sat there. The same now applies when the referenced document resolves but carries **nothing for the field its index declares** — a draft, say: the reference is discarded and reported, where before it sat in the queue and would have been indexed by a later drain once the document gained that field. Deposit the reference again after the document says what it needs to say. Carry your own data in an object with a `kind` of your own — `{ kind: "join", … }` is yours and stays yours — and never as a bare reference on its own.
|
||||
@@ -246,9 +297,9 @@ Where a call must first find out whether something already exists — a document
|
||||
|
||||
**Naming an identity proves nothing about who named it.** Any visitor may type any identifier at the barrier, or arrive on a URL that already carries one, and act as that identity — the wallet and its password are handed out on the barrier itself. The identifier is a choice of space, never a proof: anyone who knows one can act as it, so do not treat `ensureIdentity()`'s answer as an authenticated subject.
|
||||
|
||||
**The wrapped `ng` is a wrapper, and a wrapper can be wrong.** It forwards every member to the object you gave `configure`, adding two refusals of its own and nothing else. Until `1.0.0-dev.7` it did not forward them at all - every member came back as a promise, and calling one failed with NextGraph's own `<member> is not a function`. The lesson to keep is that a member of `ng` that misbehaves is worth reporting here rather than worked around: nothing in this package's own code goes through that wrapper, so this surface is the only place such a defect shows.
|
||||
**The wrapped `ng` is a wrapper, and a wrapper can be wrong.** It forwards every member to the object you gave `configure`, adding two refusals of its own and nothing else. Until `1.0.0-dev.7` it did not forward them at all - every member came back as a promise, and calling one failed with NextGraph's own `<member> is not a function`. The cause, for anyone who met it: the passthrough did `Reflect.get(ng, prop).bind(ng)`, and `@ng-org/web`'s `ng` is ITSELF a proxy whose `get` trap answers every key - `bind` included - with a fresh path-carrying callable, so `bind` never reached `Function.prototype.bind` and what came back was a Promise. It is `Reflect.apply` since `1.0.0-dev.7`, pinned by a unit test that drives a proxy-shaped `ng` rather than the plain objects every other fake in the suite is. **What it cost most was a `shape read`**, and that is the part to read if you hit it: an application that passed this `ng` to `initNg` had the ORM call `ng.orm_start_graph`, which is where the Promise landed, and `@ng-org/orm` catches that to the console - so `orm_start_graph is not a function` on the console, with a `useShapeQuery` that never leaves `isPending`, is THIS defect and not the engine refusing your shape. Pin `1.0.0-dev.7` or later and it is gone. The lesson to keep is that a member of `ng` that misbehaves is worth reporting here rather than worked around: nothing in this package's own code goes through that wrapper, so this surface is the only place such a defect shows.
|
||||
|
||||
**A shape query the engine refuses never finishes loading.** Both shape reads — `useShape` and `useShapeQuery` — open a subscription through NextGraph's ORM, and a start the engine refuses is caught there, written to the console and nothing more: the set stays empty, its readiness never settles, and the only trace is that one console line. `useShapeQuery` reports it as a `isPending` that does not end (see the guarantee above); `useShape` answers with the ORM's own two states, which do not carry the distinction - its `isLoading` says whether the initial payload has arrived and has no value for "it never will". The cause is upstream and nothing in this package can work around it. This was a total outage until the client packages caught up with the deployed broker on 2026-09-21; it is the residual failure mode, not the ordinary case. Since `1.0.0-dev.9` the commonest way in is closed for `useShapeQuery`: a document NAMED in `graphs` is opened and refused before the query is handed over, so an unreachable named document reads `isError` rather than pending for ever. What remains is the start the engine refuses for a document you can perfectly well reach, and everything under `graphs: "did:ng:i"`, where there was no name to check.
|
||||
**A shape query the engine refuses never finishes loading.** Both shape reads — `useShape` and `useShapeQuery` — open a subscription through NextGraph's ORM, and a start the engine refuses is caught there, written to the console and nothing more: the set stays empty, its readiness never settles, and the only trace is that one console line. `useShapeQuery` reports it as a `isPending` that does not end (see the guarantee above); `useShape` answers with the ORM's own two states, which do not carry the distinction - its `isLoading` says whether the initial payload has arrived and has no value for "it never will". The cause is upstream and nothing in this package can work around it, and it is MEASURED in the release this package builds against rather than inferred: in `@ng-org/orm@0.1.2-alpha.21`'s shipped `dist/connector/RdfOrmSubscription.js`, `readyPromise_` is built with `resolve` alone and nothing anywhere rejects it, `resolveReady` is called on one path only (initial data arriving), and `ng.orm_start_graph` is awaited inside a `catch` whose whole body is `console.error(e)`. This was a total outage until the client packages caught up with the deployed broker on 2026-09-21; it is the residual failure mode, not the ordinary case. Since `1.0.0-dev.9` the commonest way in is closed for `useShapeQuery`: a document NAMED in `graphs` is opened and refused before the query is handed over, so an unreachable named document reads `isError` rather than pending for ever. What remains is the start the engine refuses for a document you can perfectly well reach, and everything under `graphs: "did:ng:i"`, where there was no name to check. **So the two faults you can tell apart today are the BARRIER refusing (named document, `isError`, naming the document) and the wiring being absent (`isError` naming the `configure` field); the two you cannot are the engine rejecting your shape and the ORM never having started, which arrive as the same unending `isPending`.** A way to separate those last two has been asked for and is not decided: it would have to be invented here, since there is nothing upstream to mirror and nothing upstream to read it from.
|
||||
|
||||
**No object removal call, and no document deletion at all.** There is no `deleteObject` and no `removeDocument` at any level of NextGraph, so there is none here. You remove an object by deleting it from the LIVE set a shape query handed you - the item the set is holding, reached by iterating it, by `.first()` or by `.getBy(graph, subject)`. What that removes is the predicates your SHAPE declares on that subject: triples under a predicate the shape does not name survive, the subject survives, and the document survives. Deleting an item of a `getObjects` result removes nothing, because that result is a clone. `@ng-org/orm@0.1.2-alpha.21` ships a `removeObject(graphNuri, subjectIri)`; it is not published here, because it exists in the package and not in the source this package is verified against, and a symbol whose only evidence is what an npm build happens to export is not something to build a guarantee on.
|
||||
|
||||
@@ -280,6 +331,20 @@ Where a call must first find out whether something already exists — a document
|
||||
|
||||
**A watch on one inbox that could not be opened does not come back on its own.** Opening it can fail — a broker that does not answer in that moment — and the failure is reported rather than passed over, but what follows is event-driven and this package deliberately never polls. The watch is opened again at the next moment this identity comes to hold something it did not: it creates a document, it opens an inbox on a document, a `ReadCap` reaches it through an inbox still being watched, it reads a public-store document for the first time. A session that does none of those goes on without that inbox — deposits made into it are not applied, nothing raises, and they wait unconsumed for the next connection. The exposed case is the identity that only ever READS, since it produces none of those events, where an identity that goes on creating recovers as a by-product of its own work. Awaiting `ensureIdentity()` again applies what is waiting — it drains every one of this identity's queues before it resolves, and shows no barrier a second time — but the watching itself comes back only on a fresh page.
|
||||
|
||||
### Asked for, not decided
|
||||
|
||||
Each of these is a standing request from an application, recorded here rather than answered in a message. None is refused on principle; each is an addition to a published surface, so each waits on a decision that is this package's to make. They are listed so that an application knows it is not missing a call it simply failed to find.
|
||||
|
||||
**`configure`'s `useShape` is REQUIRED even for an application that never calls it.** `@ng-org/orm` publishes `useShape` only through its react / svelte / vue adapters, so a page with no framework can produce none of them - and the field is not optional in `EventuallyConfig`, so such an application invents a value to satisfy the signature. What it does with that value is unobservable: the injected hook is consulted in exactly one place, inside this package's own `useShape`, so an application that reads through `useShapeQuery` never reaches it. This repository's own frameworkless example passes `useShape: (() => {}) as never` (`examples/notebook/app.ts`) and reads live with `useShapeQuery` beside it; a function that THROWS is equally sound and says more if anything ever does call it. Making the field optional is a change to a published signature, hence this list rather than a fix.
|
||||
|
||||
**No published NURI type guard, and no published escaping helpers.** Neither is an oversight. On the guard: every entry takes `NuriLike` and runs its own validation at the door - permissive in, precise out - so a guard would put validation in a caller's hands where the door already does it, and invite the cast the types exist to prevent. On the escaping: `escapeIri` / `escapeLiteral` were published until the surface was narrowed, and they came out under the rule that governs this whole leaf - a symbol with no counterpart at any level of the target is a symbol an application would have to unlearn, and neither `@ng-org/web` nor the ORM exposes an equivalent (the engine escapes ad hoc where it builds SPARQL). The consequence is stated rather than denied: an application composing SPARQL against `docs.sparqlUpdate` carries its own escaper, which is a second implementation of one rule, and the one running there is not the one this package tests.
|
||||
|
||||
**No published way to write triples above `docs.sparqlUpdate`.** The write that IS published above the raw primitive is `insertObject(shapeType, object)` - the SDK's own name and signature, which takes a shape-typed object, needs its `@graph`, and composes no SPARQL at all, so it removes the query building, the escaping and the NURI validation from a caller in one move. What it costs is the ORM: it is injected through `configure` (`insertObject`), so a library that wires no `@ng-org/*` of its own cannot reach it, and it writes shape-typed objects rather than arbitrary triples. An "add these triples to this document" entry, taking triples and no shape, is what has been asked for and is not decided.
|
||||
|
||||
**No call says WHICH STORE holds a document.** Not the enumeration that `storeRegistry.listMyEntityDocs` was - that was removed in `1.0.0-dev.5` because nothing upstream lists a store's documents - but the narrower question about a document whose `reference` the caller already holds. There is no accessor for it, and the consequence is worth stating plainly: an application cannot verify its own placement, so a document written to the wrong store is undetectable from here until some reader fails to open it.
|
||||
|
||||
**No per-deposit outcome, and no count that says a queue has been drained.** `## Guarantees` says a deposit this package applies is consumed whether or not the arm succeeded, that a refusal goes to this package's own log stream, and that there is no way to ask what was lost. The gap an application feels is the one before that: nothing distinguishes "this deposit was examined and discarded" from "this deposit has not been examined yet", so a check that an ineligible deposit produced NO entry cannot be waited for - only timed out, which makes it weaker than its name. Anything observable would close it, a per-deposit outcome or merely a counter that moves when a queue has been drained; neither exists.
|
||||
|
||||
## Change policy
|
||||
|
||||
**Semver, and majors are the normal case.** This surface converges on a NextGraph that does not ship yet, so most steps toward the target remove or narrow something — the major number will move often, and that frequency is the honest signal about this package, not an apology. Refusing to version would not slow the churn down; it would only take away the one tool you have for managing it. Pin a version, upgrade deliberately, and re-pull this contract each time.
|
||||
@@ -290,11 +355,15 @@ 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.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.
|
||||
**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.15`), 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.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.
|
||||
**Tags carry the package name**, because this repository publishes more than one engagement and their versions move independently: `polyfill/v1.0.0-dev.15` 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.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.
|
||||
**THE TAG IS THE PIN, and here is exactly what exists to pin.** The acquisition channel is agreed per application and carries no version of its own, so the mechanical anchor this engagement offers is the git tag `polyfill/v<version>` on this repository, cut over the commit at which `packages/polyfill/package.json` reads `<version>`. What an application should pin is that tag, and what its `usage_` leaf's `against:` should name is the version string the tag carries - `against: @ng-eventually/polyfill@1.0.0-dev.15`. There is deliberately no promise beyond that: **nothing is published to a registry, nothing is fetchable by version, and no range resolves** - a `*` peer range against a filesystem checkout compiles whatever is checked out at the other end, uncommitted edits included, and no pin on this side can prevent that. **And the tags lag the versions.** As of 2026-10-01 the newest tag cut on this repository is `polyfill/v1.0.0-dev.3`; every version from `1.0.0-dev.4` onward exists as a package.json value on a branch and has no tag. Until the tag for a version is cut, the only pin that names a version exactly is the COMMIT at which package.json reads it - which is what a `usage_` leaf's `against:` line names in parentheses, and it is a real pin, not a workaround. Pin the tag where there is one, the commit where there is not, and say which in the `against:` line either way.
|
||||
|
||||
`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.15`, on a branch: pin that string exactly, and anchor your `usage_` leaf's `against:` on it - `against: @ng-eventually/polyfill@1.0.0-dev.15`, the string you pinned, never the version it is heading for.
|
||||
|
||||
`1.0.0-dev.15` is a **patch** (2026-10-01), and it answers a consumer's standing requests. No signature changed, nothing was added or removed from the entry, and no behaviour moved - what moved is what this leaf SAYS, which is the only channel such a request has. `## Surface` gains a complete hand-written shape, with the type argument that types the snapshot, and the statement that `initNg` discards its first argument and substitutes the published `ng` (MEASURED, not inferred). `## Guarantees` now says out loud that an anchored `docs.sparqlQuery` refuses a document it cannot read instead of swallowing it - true since `1.0.0-dev.8`, stated only in that version's release note until now - and names, in one place, which reads reach a document in a public store from nothing but its `reference` (the anchored query, always; `useShapeQuery` on a document NAMED in `graphs`, since `1.0.0-dev.9`) and which do not. `## Non-guarantees` says what the `orm_start_graph is not a function` outage actually was (this package's own `ng` wrapper, fixed in `1.0.0-dev.7`, NOT the engine refusing a shape), pins the upstream swallow to the shipped artifact it was read in, and gains an **Asked for, not decided** list so that a request an application made leaves a trace here rather than in a message. `## Change policy` says which pin exists, and that the tags lag the versions.
|
||||
|
||||
`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.
|
||||
|
||||
|
||||
+25
-11
@@ -1,7 +1,7 @@
|
||||
---
|
||||
type: usage
|
||||
summary: Binds a Refine application's resources to NextGraph documents — one shape per resource, one document per entity — through this engagement's shape reads, its shape-typed insert and its inbox
|
||||
against: 4c1801f6ee1dba0ce193e693960a6788686d602e
|
||||
against: '@ng-eventually/polyfill@1.0.0-dev.15 (tag polyfill/v1.0.0-dev.15, commit ec3900077bfd090f5388e52b47a8132b2fc68d16)'
|
||||
---
|
||||
|
||||
# usage_refine-providers-eventually — `@ng-eventually/refine-providers`
|
||||
@@ -10,7 +10,7 @@ This package is a set of [Refine](https://refine.dev/core/) providers — `dataP
|
||||
|
||||
It carries no compensation for the current state of NextGraph. Capability isolation, identity, inboxes, sharing, broker state: all of that is this engagement's. What this package does is translate — a Refine resource is one SHEX shape, a Refine `id` is the NURI of one document, and one document holds one business entity.
|
||||
|
||||
The pinned version is the engagement's own `1.0.0-dev.14`, reached through the branch it is authored on rather than a tag — see `## Frictions`.
|
||||
The pinned version is `1.0.0-dev.15`, through the tag `polyfill/v1.0.0-dev.15` that carries it.
|
||||
|
||||
## Consumed surface
|
||||
|
||||
@@ -21,7 +21,9 @@ The pinned version is the engagement's own `1.0.0-dev.14`, reached through the b
|
||||
- `graphs: ""` for a resource listing. There is no call that lists the documents of a store, so the per-document capability filter IS the listing: what comes back is what this identity holds of that shape, which is exactly what a listing should contain — the documents it created and the documents whose key it was given, with no gesture of its own to tell them apart.
|
||||
- `graphs: [<nuri>]` for one record, or several.
|
||||
|
||||
`where` and `orderBy` carry Refine's filters and sorters. `docs.sparqlQuery`, anchored, is called before a named read when the document may be a public one this identity has never read — see `## Frictions`.
|
||||
Refine's filters and sorters are applied **in memory**, over what the read returns — the engagement gives `where` and `orderBy` no published semantics, and the one guarantee it attaches to them is that an `orderBy` makes the result an array rather than a `Set`. Passing them through and claiming they carry a filter would be relying on something nothing promises.
|
||||
|
||||
`docs.sparqlQuery`, anchored, is called before a named read when the document may be a public one this identity has never read — see `## Frictions`.
|
||||
|
||||
**Writing.** `storeRegistry.createEntityDoc(store?)` with `privateStore()` / `protectedStore()` / `publicStore()` to place it, then `insertObject(shapeType, object)` with the object carrying its `@graph` and an empty `@id`. `docs.sparqlUpdate` for modification and removal, for want of anything else — see `## Needs`.
|
||||
|
||||
@@ -45,15 +47,23 @@ The pinned version is the engagement's own `1.0.0-dev.14`, reached through the b
|
||||
|
||||
**One document per entity must remain the recommended placement**, and access must stay per document. A Refine `id` is a document's NURI; if access were granted at any other granularity, `id` would stop being the unit that can be read, shared and listed.
|
||||
|
||||
**The application must inject `ormSubscription`, `getObjects`, `insertObject` and `initNg` into `configure`.** All four are optional fields of `EventuallyConfig`, and this package reaches none of the ORM on its own — it is a library, and `## Non-guarantees` states that one wiring no `@ng-org/*` of its own cannot reach the injected write. An application that omits them gets a binding whose reads and writes have no engine. `useShape` must also carry a value, being required, though this package never calls it: a function that throws says the most.
|
||||
|
||||
**And it must call THIS package's `initNg`, never the SDK's.** `## Scope` is explicit about the consequence — *« Calling `@ng-org/orm`'s `initNg` directly leaves ORM writes ungoverned »*, and `## Change policy` repeats that such an application *« still works and is still unguarded »*. That call decides which `ng` the ORM reaches the engine through, and the write guard rides on the substituted one. This is the single most consequential line of the bootstrap and the easiest to get wrong, because the wrong version works: the sibling binding calls the SDK's `initNg` today (`src/auth-provider.ts`), and a binding ported from it inherits an unguarded write path where every other guarantee still appears to hold.
|
||||
|
||||
**The application must hand this package the session.** `docs.docCreate`, `sparqlQuery` and `sparqlUpdate` all take a `sessionId` first, and the engagement gives a library no way to obtain one: a session is never built, no call takes one, `getSession` was removed, and the session arrives only in the callback passed to this package's `init`. So the application catches it there and hands it over — the same shape the sibling already has, where `authProvider.getSession()` is what the data provider calls. A library that assumed it could ask for one would have no first argument to pass.
|
||||
|
||||
Relayed to the application, which owns its URL: `?ng-id=` must survive its router. Refine's `syncWithLocation` rewrites the query string, and a dropped parameter is a silent mis-identification rather than an error.
|
||||
|
||||
## Frictions
|
||||
|
||||
**The version this engagement tells us to anchor on is not referenceable.** `## Change policy` names the regime — `polyfill/vX.Y.Z`, package-prefixed — and names `1.0.0-dev.14` as what exists today, to be pinned exactly and anchored here as `against: @ng-eventually/polyfill@1.0.0-dev.14`. The only tags that exist are `polyfill/v1.0.0-dev.2` and `polyfill/v1.0.0-dev.3`, so that string names nothing: the pin is the branch the engagement is authored on and `against:` is the commit it resolved to. Nothing about the resolution is irregular — a canonical identity mapped onto a local checkout is what a shared machine is for, and the stamp records the canonical value either way. What is missing is only the tag, and until one carries this version the policy's own instruction cannot be followed, nor can "between two pre-releases nothing is promised" be acted on: there is no pre-release to compare against.
|
||||
|
||||
**Modifying and removing a record have no published path that is both shape-typed and awaitable.** `## Non-guarantees` routes removal through the LIVE set a shape query handed back, and that set is reachable only through `useShape` — a React hook, which a `DataProvider` cannot call. Worse for this shape of caller: a mutation of a live set is batched into a call NextGraph does not await, so a refusal surfaces as an unhandled rejection in the console rather than at the line that made the change. Refine's `update` and `deleteOne` must resolve with the record or reject; neither is possible on that channel. What is left is projecting the shape into SPARQL and calling `docs.sparqlUpdate` — re-implementing in this package what NextGraph's ORM already does, which is exactly the kind of compensation this package exists not to carry.
|
||||
|
||||
**A named read needs a priming call that has no other purpose.** `getObjects` spends no round-trip asking a public store for its key, so a reference received from elsewhere reads empty until the document has been read by name through `docs.sparqlQuery`. Reading one record is therefore two calls, the first of which exists only to make the second work. `useShapeQuery` has not had this since `1.0.0-dev.9` — it asks once per document named in `graphs` — so the asymmetry between the two reads is what costs the call.
|
||||
**Reading one named record costs either two calls or a short-lived observable.** `getObjects` spends no round-trip asking a public store for its key, so a `reference` received from elsewhere reads empty through it. `## Guarantees` names the two calls that do ask — an anchored `docs.sparqlQuery`, always, and `useShapeQuery` for a document named in `scope.graphs` since `1.0.0-dev.9`, typed and reactive — so a `getOne` is either a priming query followed by `getObjects`, or a `useShapeQuery` subscribed and torn down for one read. Neither is wrong; both pay for the asymmetry between the one-shot read and the reactive one.
|
||||
|
||||
**Reading one record changes what the next listing contains.** `## Guarantees` says of a document read by name that *« the key lands in what you hold, which all three re-read on »*. A listing under `graphs: ""` is defined as what this identity holds, so opening one public document by its `reference` — a link followed into a show page — puts it in every subsequent listing of that shape, with no gesture from the application and no way to take it back. The acquisition is the engagement's intended behaviour; what is unsettled is that it makes a Refine resource grow by being browsed.
|
||||
|
||||
**A named read can stay pending for the life of the session.** `## Guarantees` keeps a named document that never synced in `isPending` deliberately — *« a barrier that was never crossed is not authority to call anything empty »* — and `## Non-guarantees` adds the shape query the engine refuses, which never finishes loading either. A `DataProvider` method must return a promise that settles; built on an observable that may never answer, `getOne` hangs and the interface shows a spinner with no end. The reference binding cannot do this: its reads are HTTP and always settle, success or failure.
|
||||
|
||||
**Naming several documents fails as a whole.** One unreachable document in `graphs` means nothing is answered, so a `getMany` over a heterogeneous list — some created here, some received, some public — cannot return the records that ARE readable. A Refine table that resolves relations in one call cannot use it.
|
||||
|
||||
@@ -63,12 +73,16 @@ Relayed to the application, which owns its URL: `?ng-id=` must survive its route
|
||||
|
||||
## Needs
|
||||
|
||||
**A shape-typed write and a shape-typed removal, awaitable, callable outside React.** The analogue of `insertObject` for modifying and removing — same posture: the object carries its `@graph`, the call rejects when refused, and the caller awaits it. This is the one need that decides the shape of this package's `dataProvider`; everything else here is a cost, this is a wall.
|
||||
**A shape-typed write and a shape-typed removal, awaitable, callable outside React.** The analogue of `insertObject` for modifying and removing — same posture: the object carries its `@graph`, the call rejects when refused, and the caller awaits it.
|
||||
|
||||
**A count, or a reachable page, on the one-shot read** — so a listing does not have to rapatriate everything an identity holds to know how many there are.
|
||||
**First, a silence to break rather than a call to add.** `## Surface` says `insertObject` fills in an `@id` *« where one was left empty »*, and says nothing about an object that arrives carrying one that already exists in its document — merge, duplicate subject, or refusal. If it merges, the modification path already exists and this need is half answered; if it duplicates, an application that tries it corrupts a document and learns so later. Either way the answer belongs in the engagement, and it is cheaper to state than to implement.
|
||||
|
||||
**A key asked for the documents named in `getObjects`'s `graphs`**, as `useShapeQuery` has done since `1.0.0-dev.9`. That would retire the priming `docs.sparqlQuery` and make one record cost one call.
|
||||
That aside, this is the need that decides the shape of this package's `dataProvider`, and the bar is set outside this pair: the binding is held to being interchangeable with `@activitypods/refine-providers`, which serves `update` and `deleteOne` as ordinary awaited calls. Every other gap this leaf records has a caller-side answer — filters, sorting, pagination and totals are computed in memory there too, a `getMany` reads one record at a time and keeps what answered, and a reference received from elsewhere is reached through `useShapeQuery`. These two have none: Refine's `update` and `deleteOne` must resolve with the record or reject, and no published call can do either.
|
||||
|
||||
**Per-document outcomes when several are named**, rather than an all-or-nothing answer — so a listing of mixed provenance can show what is readable.
|
||||
**A meaning for "delete" that a Refine interface can present.** `## Non-guarantees` is categorical: there is no object removal call and no document deletion *« at any level of NextGraph »*; what a removal takes away is the predicates the shape declares on that subject, while the subject, the document and every other triple survive. So even once the write above exists, `deleteOne` cannot make a record go away: the document stays readable, answers `isSuccess` with nothing of the shape in it, and the key already handed to someone else keeps resolving. A bookmarked show page renders an empty record rather than a missing one, where the reference binding's `DELETE` makes the next read a 404. What is needed is not only the call but a published answer to what deletion means here — whether an emptied document is meant to read as absent, and through which observation.
|
||||
|
||||
**A tag for the version `## Change policy` says to pin.**
|
||||
**A call that closes the session.** In the target, an open wallet is the authenticated state and closing it is the sign-out, so a binding wires Refine's `logout` to that act — the shape survives migration. No published call performs it: `## Guarantees` names "changing identity or clearing it" as an event that happens, `setCurrentUser` was removed on purpose, and awaiting `ensureIdentity()` again shows no barrier. The consequence is concrete and it is a loss of protection rather than an inconvenience: a Logout button can be rendered, it cannot sign anybody out, and the next visitor re-enters the previous identity in one click from a prefilled barrier. The reference binding clears its token and `check()` then fails.
|
||||
|
||||
**A display name, or something able to carry one.** This is an ASK TO WIDEN the scope, not a gap: `## Scope` excludes display names explicitly. The reason to ask anyway is that the two halves of the current answer leave a caller with no conforming option — `## Non-guarantees` forbids rendering the identifier as a readable name, and nothing published offers a name to render instead. Refine puts `getIdentity().name` in its own layout by default, and the reference binding reads a name and an avatar off the person's profile, so an interface that showed a name on one binding can show nothing lawful on this one. Anything observable would settle it: a name alongside the identifier, or a published place to keep one. Until then this package returns the identifier in that field, deliberately and against the letter of the non-guarantee, because the alternative is an empty layout.
|
||||
|
||||
**A predicate for write ownership — a comfort, not a blocker.** `## Guarantees` says only a document's owner writes to it, and `insertObject` refuses a document this identity does not own, but no published call answers whether it owns one. A listing here is everything accessible of a shape, so it legitimately mixes the two, and the default path is to let the person act and surface the refusal — which needs the awaitable write above and nothing else. What the predicate would buy is an interface that hides a control rather than letting it fail; it is worth asking for and it blocks nothing. It is not the read-side question this surface declines to answer: on the read side the answer already arrives as the absence of the record from what a read returns. It is the write side, where `## Guarantees` already expects a caller to know what it owns — *« write into documents you own, and treat a document you merely hold the key for as read-only »* — while publishing no call that says which is which.
|
||||
|
||||
@@ -18,11 +18,7 @@ consume:
|
||||
- contract: polyfill-surface
|
||||
type: git
|
||||
pullFrom: git@gitea.reconnexion.apps.gueraud.net:Reconnexion/ng-eventually.git/.project/concepts/app-contract/polyfill-surface/contract_polyfill-surface.md
|
||||
# The Change policy names the pin regime — a tag carrying the package name
|
||||
# (`polyfill/vX.Y.Z`), since that repo publishes two engagements — and names
|
||||
# `1.0.0-dev.14` as what to pin. That tag does not exist: only `polyfill/v1.0.0-dev.2`
|
||||
# and `-dev.3` are posted, so the announced version is not referenceable. Pinned on the
|
||||
# branch the engagement is authored on until the tag is cut; declared as a friction in
|
||||
# `usage_refine-providers-eventually.md`, not worked around.
|
||||
ref: caps-p1a-and-virtual-user-boundary
|
||||
# The Change policy names the pin regime: a tag carrying the package name, since that
|
||||
# repo publishes two engagements whose versions move independently.
|
||||
ref: polyfill/v1.0.0-dev.15
|
||||
into: concepts/refine-binding/polyfill-surface/
|
||||
|
||||
Reference in New Issue
Block a user