Files
ng-eventually/.project/rules/nextgraph/surface-mirrors-the-sdk.md
T

43 lines
7.2 KiB
Markdown

---
name: surface-mirrors-the-sdk
summary: Publish the SDK's names, parameters and returns — a difference must be justified beside the code
group: nextgraph
scope: local
created: 2026-09-16
---
# The published surface mirrors the SDK
**Rule:** What this package publishes exposes, as far as possible, functions of the **same NAMES**, taking the **same PARAMETERS**, returning the **same VALUES** as the target SDK. A difference is allowed — but it must be **duly justified**, and the best justification of all is that the difference is **NextGraph's own direction, as announced by its developers**. The justification lives in the symbol's provenance annotation, beside the code, never in a conversation or a prose document.
This is the surface-level sibling of [[no-divergence-from-nextgraph]], which asks *does the target do this?* of the model and the semantics. That question can be answered correctly and this one still failed: a symbol may carry the target's semantics exactly and still be published under a name the target never uses, taking a parameter the target cannot express. An application does not consume semantics. It types a name, passes an argument, reads a return — those three are what it will have to unlearn.
**Why.** The abstract statement of this rule was available for weeks and changed nothing, so what follows is what actually happened, verified in the tree.
**A name the target never uses.** `watchShape` has **zero** occurrences in `nextgraph-rs`. Worse, it spends `watch`, a verb the ORM has already spent on signal observation — `watch as watchDeepSignal` in `sdk/js/orm/src/connector/GraphOrmSubscription.ts`, `watchEffect` in the Vue adapter. So the name is not merely absent upstream, it reads as something upstream already means. The target's stated name is `useShapeQuery`.
**A parameter that had to be translated.** Its `scope` is `Scope = "public" | "protected" | "private"` (`packages/polyfill/src/model/types.ts`) — three invented literals. The SDK's own `Scope` is `{ graphs?: string[] | string; subjects?: string[] }` (`sdk/js/orm/src/types.ts`). Not equivalent, therefore **not passable through**: the polyfill has to translate one into the other, and that translation is what forced an entire home-grown read pipeline beneath it — scope resolution, document enumeration, a sync barrier, a batch read. **A parameter that must be translated is the tell that a mechanism below it is about to become ours.** The cost of a re-invented parameter is never the parameter.
**A true citation attached to the wrong claim.** `readUnion` has no counterpart upstream — it is N calls plus a batch policy — yet its head annotation reads `kind=aligned level=2 ref=sdk/js/lib-wasm/src/lib.rs:sparql_query`. That symbol exists and does what the annotation says it does. The citation is exact; the claim is false. A *composition* cannot be "aligned" on a single function. **This is the tell to watch for**, because it defeats the very thing annotations are for: a reader checks the citation, finds it correct, and moves on. The rest of the surface offers the honest form by contrast — `useShape` is annotated `kind=passthrough level=3` and the cited symbol *is* the function it forwards to. (The annotation has since been split so that the enumeration and the failure policy are marked `invention`; the head claim of alignment is still there.)
**The legitimate deviation, so nobody concludes every difference is a fault.** `watchShape` returns a `ShapeQuery` shaped like TanStack Query's `useQuery` — `{ data, isPending, isSuccess, isError, error }` — where the SDK's `useShape` returns a bare `DeepSignalSet`. That is a real difference from what the SDK ships today, and it is **justified**: NextGraph's direction, as reported by its developers, is a `useShapeQuery` returning exactly that shape. Deviating *toward* the announced target is alignment, not divergence — it is the rest of the surface that will have to catch up.
**The gap that same example exposed — closed by `announced-not-written`.** A target **stated by its developers but not yet written** has no `file:symbol` to cite: `useShapeQuery` has zero occurrences in the tree. The taxonomy in [[no-divergence-from-nextgraph]] had no kind for it either — `declared-not-wired` needs a declaration, `reserved-name` needs a name present in the tree — so an author holding the strongest possible justification had only two bad moves left: cite an approximate symbol, which is precisely the `readUnion` failure, or declare `invention`, which understates the alignment and tells a later reader the shape is ours to change. The kind `announced-not-written` is the third move: in place of a symbol upstream it cites the **record of the statement**, and it names who made it and when.
```
// @provenance useShapeQuery kind=announced-not-written level=3 ref=.project/rules/nextgraph/surface-mirrors-the-sdk.md:useShapeQuery announced-by=nextgraph-developers@2026-09-16 — the SDK ships `useShape` returning a bare `DeepSignalSet`; the announced direction is a `useShapeQuery` returning a TanStack-shaped snapshot
```
**This section is that record**, and the annotation above points back at it. The statement it holds: NextGraph's direction is a `useShapeQuery` returning `{ data, isPending, isSuccess, isError, error }`, reported from its developers and written down here on 2026-09-16 — the date the annotation carries, so a later reader can weigh how long the direction has been announced and still unwritten. An announcement nobody wrote down is hearsay: `packages/polyfill/test/provenance.test.ts` refuses an annotation of this kind whose record is missing, is a source file rather than prose, or no longer names the symbol.
**How to apply.**
- **Before naming a published symbol**, find the target's own name and use it. If the target has not named it, check the verb is *free* at the target: one it already spends elsewhere is taken, and borrowing it says something false.
- **Prefer passing the target's type through to translating it.** When a parameter cannot be passed through, stop and ask what will have to be built beneath it — that is the real decision, and it is being made by the parameter.
- **Return what the SDK returns**, unless the difference is the target's announced direction — then say whose statement it is, in the annotation's `announced-by` field and against a record the reader can open. "Whose statement" is a field the test checks, not a remark.
- **"Duly justified" has a fixed cost**: one annotation line, beside the code, naming what the target does, what was chosen instead, and why. The test is refutability — a reader must be able to check it by opening one file.
- **Check every citation against the CLAIM, not just against the tree.** Same granularity on both sides: a function may cite a function; a composition, a pipeline or a policy cites nothing at level 2 and must say so. "The reference resolves" is not "the annotation is true".
- **Applies to what is published.** An internal helper that claims to resemble the target falls under [[no-divergence-from-nextgraph]]; this rule is about the symbols an application types.
**Pending, not done.** Renaming `watchShape` to `useShapeQuery` and removing `readUnion` are **decided and not yet applied**. Read the two as live examples, not as history.