refactor: le paquet s'appelle polyfill, « SDK » désigne celui de NextGraph
Le nom @ng-eventually/sdk entrait en collision avec le SDK de NextGraph, dont ce paquet est justement un polyfill. Impossible d'écrire « le SDK » sans lever l'ambiguïté à chaque phrase — et le contrat publié, lu par une application, était le pire endroit pour laisser traîner ça. packages/sdk → packages/polyfill, @ng-eventually/sdk → @ng-eventually/polyfill, contract_sdk-surface → contract_polyfill-surface, e2e/sdk-entry.ts → e2e/polyfill-entry.ts, docs/sdk-reference.md → docs/polyfill-reference.md. Les occurrences de « SDK » qui désignent celui de NextGraph restent intactes, y compris les chemins dans nextgraph-rs (sdk/js/orm, sdk/js/web). Le tri s'est fait occurrence par occurrence, pas par substitution. Le contrat énonce désormais son identité en une phrase : « This package is a polyfill of NextGraph's SDK. »
This commit is contained in:
+17
-17
@@ -1,10 +1,10 @@
|
||||
# API contract — what `@ng-eventually/sdk` exposes today, and what the future SDK should expose per subject
|
||||
# API contract — what `@ng-eventually/polyfill` exposes today, and what the future SDK should expose per subject
|
||||
|
||||
> **Updated 2026-08-03, after the source layout was reorganised by migration fate** (`docs/source-layout-by-fate.md`). Paths, and three names, changed under this document: `readModel` became the directly-exported `readUnion`; `accounts` / `AccountRecord` / `AccountStorage` became `virtualUsers` / `VirtualUserRecord` / `VirtualUserStorage` (module `shared-wallet/virtual-users.ts`); `store-registry-api.ts` became `surface/placement.ts`. Two modules were created and are covered here: `emulated-verifier/branch-registers.ts` (the four durable registers, split out of the shim) and `shared-wallet/bootstrap.ts` (the injection store, split out of the `/polyfill` entry). The subject-by-subject rulings below are unaffected — what moved is where the code lives, not what it promises.
|
||||
|
||||
**Scope: the APP-FACING contract only.** Everything reachable from the two published entry points, and nothing else. The library's internal modules — the shim machinery, the read paths, the boundary guards — are held to the same standard (as close as possible to what NextGraph does or plans) but have their own document, `docs/internal-contract.md`: a consumer never reads that one, a maintainer does. This split was made on 2026-08-03, together with the export change described in § 15.
|
||||
|
||||
**Scope.** The real exported surface of `@ng-eventually/sdk` (verified against the `export` statements in `packages/sdk/src/index.ts` and `packages/sdk/src/polyfill.ts` — `package.json` maps exactly two entry points, `.` and `./polyfill`), and, for each subject, the target signature the future NextGraph JS SDK is expected to expose. Written 2026-08-03, verified against the `nextgraph-rs` clone (HEAD `213338f6`, 2026-05-16) and the installed `@ng-org/web@0.1.2-alpha.13` type declarations (`node_modules/.bun/@ng-org+web@0.1.2-alpha.13/node_modules/@ng-org/web/dist/index.d.ts`, hereafter `index.d.ts`).
|
||||
**Scope.** The real exported surface of `@ng-eventually/polyfill` (verified against the `export` statements in `packages/polyfill/src/index.ts` and `packages/polyfill/src/polyfill.ts` — `package.json` maps exactly two entry points, `.` and `./polyfill`), and, for each subject, the target signature the future NextGraph JS SDK is expected to expose. Written 2026-08-03, verified against the `nextgraph-rs` clone (HEAD `213338f6`, 2026-05-16) and the installed `@ng-org/web@0.1.2-alpha.13` type declarations (`node_modules/.bun/@ng-org+web@0.1.2-alpha.13/node_modules/@ng-org/web/dist/index.d.ts`, hereafter `index.d.ts`).
|
||||
|
||||
**How to read the epistemic labels.** Every target-side claim carries one of:
|
||||
|
||||
@@ -19,7 +19,7 @@ Per the design principle (`README.md` § *Design principle*): an absent implemen
|
||||
|
||||
## 1. Bootstrap and configuration
|
||||
|
||||
### Today — `@ng-eventually/sdk`: **one call**
|
||||
### Today — `@ng-eventually/polyfill`: **one call**
|
||||
|
||||
```ts
|
||||
// shared-wallet/bootstrap.ts
|
||||
@@ -57,7 +57,7 @@ So an application's whole bootstrap is `configure({ … })` plus `await ensureId
|
||||
|
||||
## 2. Lifecycle
|
||||
|
||||
### Today — `@ng-eventually/sdk`
|
||||
### Today — `@ng-eventually/polyfill`
|
||||
|
||||
```ts
|
||||
// lifecycle.ts:11 — forwards to the real @ng-org/web init injected at configure()
|
||||
@@ -94,7 +94,7 @@ Divergence: none in behaviour (pure forwarding), but the wrapper erases the para
|
||||
|
||||
## 2bis. The access gate — `ensureIdentity`
|
||||
|
||||
### Today — `@ng-eventually/sdk`
|
||||
### Today — `@ng-eventually/polyfill`
|
||||
|
||||
```ts
|
||||
export async function ensureIdentity(): Promise<PrincipalId>; // shared-wallet/access-gate.ts
|
||||
@@ -123,7 +123,7 @@ Why it lives here and not in the consumer application: the first consumer had ~3
|
||||
|
||||
## 3. The `ng` object
|
||||
|
||||
### Today — `@ng-eventually/sdk`
|
||||
### Today — `@ng-eventually/polyfill`
|
||||
|
||||
```ts
|
||||
// index.ts:55
|
||||
@@ -147,7 +147,7 @@ The two overrides:
|
||||
|
||||
## 4. Reactive typed reads — `useShape`
|
||||
|
||||
### Today — `@ng-eventually/sdk`
|
||||
### Today — `@ng-eventually/polyfill`
|
||||
|
||||
```ts
|
||||
// use-shape.ts:12
|
||||
@@ -185,7 +185,7 @@ Divergence to note: the wrapper types everything `unknown`, losing the generic `
|
||||
|
||||
## 5. Reactive typed reads with load state — `watchShape`
|
||||
|
||||
### Today — `@ng-eventually/sdk`
|
||||
### Today — `@ng-eventually/polyfill`
|
||||
|
||||
```ts
|
||||
// watch-shape.ts:73
|
||||
@@ -226,7 +226,7 @@ So the constraint on the bet: the target can already answer "synced?" (`readyPro
|
||||
|
||||
## 6. One-shot listing — the read-model
|
||||
|
||||
### Today — `@ng-eventually/sdk`
|
||||
### Today — `@ng-eventually/polyfill`
|
||||
|
||||
```ts
|
||||
// read-model.ts:60
|
||||
@@ -264,7 +264,7 @@ The anchored-read mechanics are level-1 VERIFIED: an anchor restricts the query
|
||||
|
||||
## 7. Raw document / SPARQL primitives — `docs.*`
|
||||
|
||||
### Today — `@ng-eventually/sdk` (namespace `docs`)
|
||||
### Today — `@ng-eventually/polyfill` (namespace `docs`)
|
||||
|
||||
```ts
|
||||
// docs.ts:46
|
||||
@@ -324,7 +324,7 @@ So the target's direction for scope placement is **already visible in the source
|
||||
|
||||
## 8. Per-document subscription — `subscribeDoc`
|
||||
|
||||
### Today — `@ng-eventually/sdk`
|
||||
### Today — `@ng-eventually/polyfill`
|
||||
|
||||
```ts
|
||||
// subscribe.ts:47,60,79
|
||||
@@ -362,7 +362,7 @@ declare function doc_subscribe(repo_o: string, session_id: any, callback: Functi
|
||||
|
||||
## 9. Inbox — deposits, and cap delivery
|
||||
|
||||
### Today — `@ng-eventually/sdk` (namespace `inbox`)
|
||||
### Today — `@ng-eventually/polyfill` (namespace `inbox`)
|
||||
|
||||
```ts
|
||||
export interface Deposit {
|
||||
@@ -417,7 +417,7 @@ Consequences per function:
|
||||
### Today
|
||||
|
||||
```ts
|
||||
// @ng-eventually/sdk — model/types.ts. The published cap surface is now ONE type.
|
||||
// @ng-eventually/polyfill — model/types.ts. The published cap surface is now ONE type.
|
||||
export type Nuri = `did:ng:${string}`;
|
||||
export type NuriLike = Nuri | string;
|
||||
|
||||
@@ -504,7 +504,7 @@ The escaping helpers were published until the surface was narrowed. Their remova
|
||||
|
||||
## 12. Scope resolution, per-entity documents, and the store registry
|
||||
|
||||
### Today — `@ng-eventually/sdk` (namespace `storeRegistry`) — plus `Scope` from `types.ts`
|
||||
### Today — `@ng-eventually/polyfill` (namespace `storeRegistry`) — plus `Scope` from `types.ts`
|
||||
|
||||
> **Narrowed twice.** 2026-08-03 the entry stopped re-exporting the whole `store-registry` module and kept an app-facing slice (`src/surface/placement.ts`). 2026-08-05 that slice lost its two inbox-ADDRESS functions as well: an application deposits with `inbox.postToDocument(doc, …)` and shares with `inbox.share(doc, toUser)` — always naming a document or a person, never an address, because upstream an address is resolved from a profile and never handled by a caller. **Five functions remain published**, listed first below; everything after them is kept for the record and is covered by `docs/internal-contract.md`.
|
||||
|
||||
@@ -599,7 +599,7 @@ declare function user_disconnect(user_id: string): Promise<void>;
|
||||
|
||||
## 14. Type re-exports
|
||||
|
||||
`@ng-eventually/sdk` re-exports, type-only (erased at build, `src/index.ts`):
|
||||
`@ng-eventually/polyfill` re-exports, type-only (erased at build, `src/index.ts`):
|
||||
|
||||
```ts
|
||||
export type { ShapeType, BaseType, Schema } from "@ng-org/shex-orm";
|
||||
@@ -658,9 +658,9 @@ Exported, but not SDK surface. Coding against these builds knowledge that migrat
|
||||
|
||||
## Appendix — full export inventory (for diffing)
|
||||
|
||||
*Generated from the `export` statements, and pinned by `packages/sdk/test/vocabulary.test.ts` — if this list and the code disagree, that test fails. It went stale once, still listing `storeRegistry`'s shim internals after the entry had been narrowed, which is what a hand-maintained inventory does.*
|
||||
*Generated from the `export` statements, and pinned by `packages/polyfill/test/vocabulary.test.ts` — if this list and the code disagree, that test fails. It went stale once, still listing `storeRegistry`'s shim internals after the entry had been narrowed, which is what a hand-maintained inventory does.*
|
||||
|
||||
### `@ng-eventually/sdk` — `src/index.ts` (the only entry since 2026-08-07)
|
||||
### `@ng-eventually/polyfill` — `src/index.ts` (the only entry since 2026-08-07)
|
||||
|
||||
```text
|
||||
direct: BaseType, DeepSignalSet, DocChange, DocChangeType, EventuallyConfig, NG, NgLike, Nuri, NuriLike, PrincipalId, RegistrySession, Schema, Scope, ShapeObservable, ShapeQuery, ShapeType, SharedWalletConfig, UnionSubject, Unsubscribe, UseShapeLike, configure, docChangeType, ensureIdentity, init, initNg, ng, readUnion, subscribeDoc, subscribeDocs, useShape, watchShape
|
||||
|
||||
@@ -47,7 +47,7 @@ breaks writes with `RepoNotFound`). See the scope rule in
|
||||
|
||||
*The decision stands; the mechanism named in it has been replaced.* Opening was
|
||||
`orm_start_graph` when this was written. It is now `ensureRepoOpen` — `doc_subscribe`
|
||||
plus a wait for the first `State` (`packages/sdk/src/emulated-verifier/open-repo.ts:167`) — after
|
||||
plus a wait for the first `State` (`packages/polyfill/src/emulated-verifier/open-repo.ts:167`) — after
|
||||
`orm_start_graph` was found to hang on a fan-out (`subscribe.ts:28,181`). What must be
|
||||
read here is the invariant *"open the repo, by its store NURI, before writing"*, not the
|
||||
call that used to implement it.
|
||||
|
||||
@@ -70,13 +70,13 @@ The surface consequence: the *act* — obtain a link, circulate it — is the sa
|
||||
|
||||
## 5. Recommendation for the polyfill
|
||||
|
||||
**SUPERSEDED on its first point, 2026-08-06 — `linkTo` was REMOVED, and the removal stands.** This section used to open: *"The surface already exists: `linkTo(doc: NuriLike): ReadCap` … `inbox.share(doc, toUser)` for directed delivery. **Keep `linkTo` — the act is the right one**"*. What that recommendation missed is the single thing § 0 of `docs/readcap-and-nuri-model.md` exists to hold: a call that returns a document's **key** where a caller asked for its **reference** converts *"whoever has the reference AND the key reads"* into *"whoever has the reference reads"* — for that document and for every document it mentions — so confidentiality can no longer be composed inside anything one circulates. The *act* (circulate a value that opens a document) may still be right; making it the answer to "give me the link to my document" was not. The reasoning is recorded where the function was, in the `No linkTo here` block of `packages/sdk/src/surface/placement.ts`.
|
||||
**SUPERSEDED on its first point, 2026-08-06 — `linkTo` was REMOVED, and the removal stands.** This section used to open: *"The surface already exists: `linkTo(doc: NuriLike): ReadCap` … `inbox.share(doc, toUser)` for directed delivery. **Keep `linkTo` — the act is the right one**"*. What that recommendation missed is the single thing § 0 of `docs/readcap-and-nuri-model.md` exists to hold: a call that returns a document's **key** where a caller asked for its **reference** converts *"whoever has the reference AND the key reads"* into *"whoever has the reference reads"* — for that document and for every document it mentions — so confidentiality can no longer be composed inside anything one circulates. The *act* (circulate a value that opens a document) may still be right; making it the answer to "give me the link to my document" was not. The reasoning is recorded where the function was, in the `No linkTo here` block of `packages/polyfill/src/surface/placement.ts`.
|
||||
|
||||
What an application does instead, today: it names a document with the bare reference it already holds — every published call returns one — and grants access with `inbox.share(doc, toUser)` (`packages/sdk/src/surface/inbox.ts`). The four points below are kept and re-read against that: 1 and 4 stand as written for **any** future link-producing surface; 2 is void with the function; 3 is unchanged and still open.
|
||||
What an application does instead, today: it names a document with the bare reference it already holds — every published call returns one — and grants access with `inbox.share(doc, toUser)` (`packages/polyfill/src/surface/inbox.ts`). The four points below are kept and re-read against that: 1 and 4 stand as written for **any** future link-producing surface; 2 is void with the function; 3 is unchanged and still open.
|
||||
|
||||
1. **Label any such call LEVEL-1 SHAPE in `docs/api-contract.md`.** What supports it: the `NgLink` family and its stated sharing flow, the `PermaShare` permission, the exercised object-URL and profile-QR precedents, and the PO doctrine that circulation is the only distribution. The model's own stated flows are unusable without *some* produce-a-link affordance, which is as much confidence as an unbuilt feature allows. What cannot be promised: the SDK's name for it, sync vs async (upstream link-building needs overlay + peers from the session, so async is plausible — same adapter-sized delta class as `subscribeDoc`'s sync unsubscribe), and whether the value is a NURI string or a structured link. Therefore: **the returned value is opaque**; a consumer that stores it, transmits it, and hands it back unmodified learns nothing to unlearn; a consumer that parses it does.
|
||||
2. ~~**Fix the comment-vs-code mismatch in `linkTo`.**~~ **VOID — the function is gone.** The observation it rested on survives and is worth keeping: `RepoLinkV0`-with-key IS the protected-document link, and circulating it out-of-band is the documented normal case (`:5059`), so a protected link carrying its key is not in itself a violation — with the § 4 durability caveat. What made `linkTo` wrong was not that the value carried a key; it was that a caller got one **by asking for a reference**. Handing over a key must be its own act, which is what `inbox.share` is.
|
||||
3. **The recipient verb is missing.** Nothing exported ingests an out-of-band link: `learn` is reached only by inbox processing and the connection drain (`packages/sdk/src/surface/inbox.ts:410`, `packages/sdk/src/emulated-verifier/connect.ts:68`), and `getCaps()` is documented machinery (api-contract §15). The model names the recipient act precisely — open the link: load the repo from its read cap, file `AddLink` durably on the User branch, subscribe (`:5059`; `engine/repo/src/types.rs:1934-1950`; `verifier.rs:2237`). Suggested surface, same epistemic label as point 1: `openLink(link: string): Promise<Nuri>` — files the cap in the emulated registers and returns the cap-less target for use in reads. Without it, path 2 has a producer and no consumer, and the multi-actor test where Bob *obtains* the document through calls (never through a shared variable) cannot be written — the exact failure mode `rules/engineering/multi-actor-tests-obtain-not-receive.md` records.
|
||||
3. **The recipient verb is missing.** Nothing exported ingests an out-of-band link: `learn` is reached only by inbox processing and the connection drain (`packages/polyfill/src/surface/inbox.ts:410`, `packages/polyfill/src/emulated-verifier/connect.ts:68`), and `getCaps()` is documented machinery (api-contract §15). The model names the recipient act precisely — open the link: load the repo from its read cap, file `AddLink` durably on the User branch, subscribe (`:5059`; `engine/repo/src/types.rs:1934-1950`; `verifier.rs:2237`). Suggested surface, same epistemic label as point 1: `openLink(link: string): Promise<Nuri>` — files the cap in the emulated registers and returns the cap-less target for use in reads. Without it, path 2 has a producer and no consumer, and the multi-actor test where Bob *obtains* the document through calls (never through a shared variable) cannot be written — the exact failure mode `rules/engineering/multi-actor-tests-obtain-not-receive.md` records.
|
||||
4. **Do not add**: link options (expiry, audience, revoke-this-link), per-reader introspection for public documents, or any API that parses or inspects a link's insides — nothing upstream supports any of them, and each teaches a lever the model does not have.
|
||||
|
||||
## 6. The question for the NextGraph developer
|
||||
|
||||
@@ -37,7 +37,7 @@ Interpretation (**plausible mechanism, not settled**): the write was pushed into
|
||||
|
||||
## What the SDK exposes but does not consume
|
||||
|
||||
`disconnections_subscribe` **does fire** on this failure — but neither the polyfill (`@ng-eventually/sdk`) nor the consumer app subscribes to it. The signal exists, nobody listens to it; on the app side, no mechanism retries or warns the user.
|
||||
`disconnections_subscribe` **does fire** on this failure — but neither the polyfill (`@ng-eventually/polyfill`) nor the consumer app subscribes to it. The signal exists, nobody listens to it; on the app side, no mechanism retries or warns the user.
|
||||
|
||||
## Scope & not reproduced
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# Internal contract — what `@ng-eventually/sdk` keeps off its surface, and what NextGraph does or would do about each subject
|
||||
# Internal contract — what `@ng-eventually/polyfill` keeps off its surface, and what NextGraph does or would do about each subject
|
||||
|
||||
> **Updated 2026-08-03, after the source layout was reorganised by migration fate** (`docs/source-layout-by-fate.md`). Paths, and three names, changed under this document: `readModel` became the directly-exported `readUnion`; `accounts` / `AccountRecord` / `AccountStorage` became `virtualUsers` / `VirtualUserRecord` / `VirtualUserStorage` (module `shared-wallet/virtual-users.ts`); `store-registry-api.ts` became `surface/placement.ts`. Two modules were created and are covered here: `emulated-verifier/branch-registers.ts` (the four durable registers, split out of the shim) and `shared-wallet/bootstrap.ts` (the injection store, split out of the `/polyfill` entry). The subject-by-subject rulings below are unaffected — what moved is where the code lives, not what it promises.
|
||||
|
||||
**Scope.** The complement of [`docs/api-contract.md`](./api-contract.md): every module export under `packages/sdk/src/` that is NOT reachable from the two published entry points (`package.json` maps exactly `.` → `src/index.ts` and `./polyfill` → `src/polyfill.ts`). A consumer never reads this document; a maintainer does. The internal code is held to the same standard as the surface — as close as possible to what NextGraph does or plans — so every subject below carries the same target-side analysis. Written 2026-08-04, verified against the `nextgraph-rs` clone (HEAD `213338f6`) and the installed `@ng-org/web@0.1.2-alpha.13` declarations (`node_modules/.bun/@ng-org+web@0.1.2-alpha.13/node_modules/@ng-org/web/dist/index.d.ts`, hereafter `index.d.ts`).
|
||||
**Scope.** The complement of [`docs/api-contract.md`](./api-contract.md): every module export under `packages/polyfill/src/` that is NOT reachable from the two published entry points (`package.json` maps exactly `.` → `src/index.ts` and `./polyfill` → `src/polyfill.ts`). A consumer never reads this document; a maintainer does. The internal code is held to the same standard as the surface — as close as possible to what NextGraph does or plans — so every subject below carries the same target-side analysis. Written 2026-08-04, verified against the `nextgraph-rs` clone (HEAD `213338f6`) and the installed `@ng-org/web@0.1.2-alpha.13` declarations (`node_modules/.bun/@ng-org+web@0.1.2-alpha.13/node_modules/@ng-org/web/dist/index.d.ts`, hereafter `index.d.ts`).
|
||||
|
||||
**How the boundary was computed — mechanically, from the `export` statements.** `index.ts` re-exports wholesale (`export *` / `export * as ns`) from `types.ts`, `inbox.ts`, `docs.ts`, `surface/read-model.ts`, and by name everything `surface/use-shape.ts`, `surface/watch-shape.ts`, `lifecycle.ts`, `sparql.ts` export, and `subscribeDoc`/`subscribeDocs`/`docChangeType` (+ types) from `subscribe.ts`; its `storeRegistry` namespace is the **`surface/placement.ts` slice only** (`createEntityDoc`, `listMyEntityDocs`, `resolveScopeGraph`, `resolveWriteGraph`, `openDocumentInbox`). `model/nuri.ts` is now internal in full — the guards `isNuri`/`hasReadCap` stopped being published when the entries became permissive-in (`NuriLike` validated at the door). *(The second entry, `polyfill.ts`, was merged into `index.ts` on 2026-08-07, and the polyfill-era block was then cut to ONE published call, `configure`. `configureStoreRegistry`, `setCurrentUser` and `connectedUser` became internal the same day — folded, replaced by the gate, and awaited inside it respectively; the machinery accessors and test resets are internal too. All four are inventoried below.)* Everything else that carries `export` in a `src/` module is internal and inventoried here. Eight modules are internal in their entirety: `shared-wallet/access-log.ts`, `emulated-verifier/machinery.ts`, `surface/ng-proxy.ts`, `emulated-verifier/open-repo.ts`, `shared-wallet/outbox-log.ts`, `shared-wallet/physical.ts`, `emulated-verifier/reach.ts`, `emulated-verifier/read-filter.ts`. Four are internal in part: `nuri.ts`, `emulated-verifier/connect.ts`, `subscribe.ts`, `shared-wallet/account-registry.ts`.
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@ The consumer application imports `@ng-org/web` / `@ng-org/orm` resolved to this
|
||||
via a build alias during the polyfill period. Removing the alias makes those imports
|
||||
resolve to the real SDK — the `ng`/`useShape`/`inbox` surface is SDK-identical, so
|
||||
no consumer code changes. The one non-SDK call — `configure(...)` /
|
||||
the POLYFILL-ERA block of `@ng-eventually/sdk` — is deleted. The lib itself disappears.
|
||||
the POLYFILL-ERA block of `@ng-eventually/polyfill` — is deleted. The lib itself disappears.
|
||||
|
||||
## The one break already taken: `declareConnections`
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Current-state NextGraph — what the SDK/broker do and do NOT expose
|
||||
|
||||
**Owner:** this library. `@ng-eventually/sdk` exists because the *current*
|
||||
**Owner:** this library. `@ng-eventually/polyfill` exists because the *current*
|
||||
NextGraph JS SDK is immature. This file is the authoritative reference on what
|
||||
today's SDK/broker actually give us — the ground truth every polyfill in this
|
||||
lib compensates for. Read [`simulation.md`](./simulation.md) for how we emulate
|
||||
@@ -333,7 +333,7 @@ has both**:
|
||||
- **Subscribable with a sync BARRIER.** `doc_subscribe(nuri)` delivers `TabInfo` then
|
||||
an initial **`State`** (`verifier.rs:470`/`:476`); that first `State` is the sync
|
||||
barrier — **after it, presence is guaranteed and absence is definitive** (pinned
|
||||
empirically by CONTRACT 3 in `packages/sdk/e2e/`). But this barrier exists only
|
||||
empirically by CONTRACT 3 in `packages/polyfill/e2e/`). But this barrier exists only
|
||||
for a repo `doc_subscribe` can open, i.e. a `did:ng:o:<RepoID>` repo. A **store-root
|
||||
has no first-`State` barrier**: an anchored read on it can return 0 rows during
|
||||
sync-lag with no signal distinguishing "still syncing" from "genuinely empty".
|
||||
@@ -757,7 +757,7 @@ chain never runs and consumers keep a stale value until the next connection
|
||||
delivers a fresh initial `State`. REMOTE commits DO push correctly (verified:
|
||||
cross-browser reactive update works). Verdict pending a live instrumented run.
|
||||
Full write-up (suspect link, instrumentation, planned polyfill-side fix):
|
||||
[`../packages/sdk/docs/sdk-reference.md`](../packages/sdk/docs/sdk-reference.md)
|
||||
[`../packages/polyfill/docs/polyfill-reference.md`](../packages/polyfill/docs/polyfill-reference.md)
|
||||
§ *Current emulation status*.
|
||||
|
||||
### Cold-start anchored read returns 0 rows instead of an error — symptom VERIFIED, mechanism INFERRED, healed polyfill-side
|
||||
@@ -766,7 +766,7 @@ On a FRESH session over the SAME persistent wallet (reconnect, new page, re-logi
|
||||
anchored `sparql_query` against a document written in an earlier session comes back with
|
||||
**0 rows and no error** — persisted documents read as empty. Observed on every anchored
|
||||
reader of the polyfill and healed identically in each (`ensureRepoOpen` before the read,
|
||||
`packages/sdk/src/emulated-verifier/open-repo.ts`): the user's own documents,
|
||||
`packages/polyfill/src/emulated-verifier/open-repo.ts`): the user's own documents,
|
||||
the user's store (`shared-wallet/account-registry.ts` `readUserStore`), the by-need doc batch
|
||||
(`surface/read-model.ts` `readUnion`), and the store-root pointer read (`shared-wallet/account-registry.ts`
|
||||
`resolvePointer`). The heal is `doc_subscribe(nuri)` → await the first `State` (the sync
|
||||
@@ -802,7 +802,7 @@ is a single account subject carrying MULTIPLE values for one scope predicate (ob
|
||||
five `shim:docPublic`), after which a writer and a later reader can resolve DIFFERENT
|
||||
scope docs and the reader's anchored read returns 0.
|
||||
|
||||
Two polyfill-side guards, both in `packages/sdk/src/shared-wallet/account-registry.ts`: `ensureInFlight`
|
||||
Two polyfill-side guards, both in `packages/polyfill/src/shared-wallet/account-registry.ts`: `ensureInFlight`
|
||||
(a bounded promise map keyed by account, so concurrent `ensureAccount` calls share ONE
|
||||
resolve-or-provision) prevents new forks; `canonicalDoc` (pick the lexicographically
|
||||
smallest NURI among all distinct values for a scope predicate — NURIs are
|
||||
|
||||
+3
-3
@@ -5,7 +5,7 @@ shared wallet. This is a design decision, grounded entirely in the query
|
||||
capability documented in
|
||||
[`nextgraph-current-state.md`](./nextgraph-current-state.md) § *The query
|
||||
capability*. The consumer application never sees any of this: it asks
|
||||
`@ng-eventually/sdk` for its lists by need and trusts the answer — the whole
|
||||
`@ng-eventually/polyfill` for its lists by need and trusts the answer — the whole
|
||||
read mechanism lives here, in the polyfill.
|
||||
|
||||
> The rule in one line: read each by-need doc with its own anchored
|
||||
@@ -111,7 +111,7 @@ Keep the reactive ORM strictly to already-opened single stores; it is a change
|
||||
|
||||
## The boundary with the consumer application
|
||||
|
||||
The consumer application asks the SDK for its lists by need and trusts the returned
|
||||
The consumer application asks the polyfill for its lists by need and trusts the returned
|
||||
set. It never constructs a NURI, never picks the union-vs-anchor mode, never touches
|
||||
the ORM. The domain-shaped list helpers (e.g. "my meeting points", "events") live in
|
||||
the consumer application, not the lib; the lib exposes the generic by-need read.
|
||||
@@ -172,7 +172,7 @@ and never iterates the other named graphs. (A repo absent from `self.repos` thro
|
||||
`RepoNotFound` and is skipped per-doc, see the VERIFIED note above — the read cannot
|
||||
sync an unknown repo.)
|
||||
|
||||
> **Re-confirmed by the standing e2e harness (`packages/sdk/e2e/`, broker
|
||||
> **Re-confirmed by the standing e2e harness (`packages/polyfill/e2e/`, broker
|
||||
> `@ng-org/web 0.1.2-alpha.13`).** The `docRoundTrip` check measures all three shapes
|
||||
> anchored to a doc D: (a) a no-GRAPH default-graph write round-trips; (b) an explicit
|
||||
> `INSERT DATA { GRAPH <D> {…} }` — a **constant** plain NURI — **also** round-trips
|
||||
|
||||
@@ -378,7 +378,7 @@ One thing a consumer must not conclude from the emulation: that a public documen
|
||||
|
||||
## 5. What the polyfill emulates (caps.ts) — and where it still diverges
|
||||
|
||||
**Realigned 2026-07-28 (batch P1a).** `packages/sdk/src/emulated-verifier/caps.ts` used to model `readers: Map<Nuri, Set<PrincipalId>>` + `grantRead(doc, grantee)` — a per-document **ACL of principals**, the exact INVERSION of the real model. It now records, **per identity**, the caps that identity holds (`Map<Nuri, ReadCap>`) — whose only question is `capFor(nuri)` — and `nuri.ts` carries the cap-less / cap-bearing distinction on the `r:` segment. The durable registers are emulated in `shared-wallet/account-registry.ts` (`readCap` on the Store branch, `link` on the User branch); this in-memory record is their cache.
|
||||
**Realigned 2026-07-28 (batch P1a).** `packages/polyfill/src/emulated-verifier/caps.ts` used to model `readers: Map<Nuri, Set<PrincipalId>>` + `grantRead(doc, grantee)` — a per-document **ACL of principals**, the exact INVERSION of the real model. It now records, **per identity**, the caps that identity holds (`Map<Nuri, ReadCap>`) — whose only question is `capFor(nuri)` — and `nuri.ts` carries the cap-less / cap-bearing distinction on the `r:` segment. The durable registers are emulated in `shared-wallet/account-registry.ts` (`readCap` on the Store branch, `link` on the User branch); this in-memory record is their cache.
|
||||
|
||||
| | Real NextGraph | caps.ts emulation (post-P1a) |
|
||||
|---|---|---|
|
||||
|
||||
+4
-4
@@ -11,7 +11,7 @@
|
||||
> file for *how* each emulation works; read those two for *what is fake* and *what
|
||||
> replaces it*.
|
||||
|
||||
The consumer application writes against `@ng-eventually/sdk` as if NextGraph
|
||||
The consumer application writes against `@ng-eventually/polyfill` as if NextGraph
|
||||
already shipped per-entity documents in public/protected/private stores, capabilities
|
||||
and inboxes. It hasn't (see [`nextgraph-current-state.md`](./nextgraph-current-state.md)).
|
||||
This file is the lib's own engineering doctrine on how it fabricates that mature
|
||||
@@ -187,7 +187,7 @@ virtual/physical distinction dissolves (see [`migration-guide.md`](./migration-g
|
||||
### SDK-shaped scope resolvers — the consumer application holds no store-id
|
||||
|
||||
The consumer application must never construct a `did:ng:${store_id}` NURI itself:
|
||||
physical placement is the lib's job (the whole point of the SDK boundary). Two
|
||||
physical placement is the lib's job (the whole point of the polyfill boundary). Two
|
||||
resolvers turn a logical scope into an opaque graph NURI without exposing any
|
||||
store-id:
|
||||
|
||||
@@ -452,7 +452,7 @@ the target's behaviour.
|
||||
The old item-level application-visibility filter (the since-deleted `isolation.ts`
|
||||
`applyIsolation`, a `Set`-of-records filter keyed on owner+scope) is retired
|
||||
from the consumer path: the application carries no access logic — it declares its
|
||||
identity and shares caps, and trusts the SDK. Its matrix functions are
|
||||
identity and shares caps, and trusts the polyfill. Its matrix functions are
|
||||
dead scaffolding kept for reference and removed at migration. There is no longer a
|
||||
second, coexisting app-layer filter to reconcile — the single axis is the
|
||||
per-document cap, exactly as in the target.
|
||||
@@ -639,5 +639,5 @@ document trust root):
|
||||
(`did:ng:...`): validates and throws on IRI-breaking chars rather than emitting
|
||||
a malformed/injected query.
|
||||
|
||||
These are re-exported from `@ng-eventually/sdk` so the consumer application
|
||||
These are re-exported from `@ng-eventually/polyfill` so the consumer application
|
||||
reuses the same escaping when it builds SPARQL.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Source layout by migration fate — analysis and recommendation
|
||||
|
||||
**Status: ADOPTED.** The layout this document recommends is the one in the tree — `model/`, `surface/`, `emulated-verifier/`, `shared-wallet/`. Read the rest as the reasoning that produced it, not as a proposal. *(Two things have moved under it since: the package became `packages/sdk` / `@ng-eventually/sdk` on 2026-08-07 — it was `packages/client` / `@ng-eventually/client`, kept here as a search keyword — and `emulated-verifier/public-store.ts` was added on 2026-08-06. Paths below point at where the files are now.)*
|
||||
**Status: ADOPTED.** The layout this document recommends is the one in the tree — `model/`, `surface/`, `emulated-verifier/`, `shared-wallet/`. Read the rest as the reasoning that produced it, not as a proposal. *(Two things have moved under it since: the package became `packages/polyfill` / `@ng-eventually/polyfill` on 2026-08-10 — it was `packages/client` / `@ng-eventually/client`, kept here as a search keyword — and `emulated-verifier/public-store.ts` was added on 2026-08-06. Paths below point at where the files are now.)*
|
||||
|
||||
Written 2026-08-04, when nothing had yet been moved, from the source of `src/` (25 modules), the two contracts (`docs/api-contract.md`, `docs/internal-contract.md`) used as the export-level inventory, and the read-only `nextgraph-rs` clone (HEAD `213338f6`); the upstream facts this analysis leans on were re-verified at the source and are cited with layer numbers per `README.md` § *The three references* (1 = engine, 2 = wasm binding, 3 = JS ORM).
|
||||
|
||||
@@ -136,7 +136,7 @@ Full mixed list: `docs.ts`, `surface/read-model.ts`, `inbox.ts`, `subscribe.ts`,
|
||||
|
||||
- All 23 non-entry `src/` modules import each other relatively; every moved file churns its importers' paths (mechanical, type-checked).
|
||||
- 19 of 19 unit-test files deep-import `../src/*` — 66 static import lines (heaviest: `store-registry` ×16, `polyfill` ×14) **plus 3 dynamic `await import("../src/…")` sites** (`test/isolation-active.test.ts:359`, `test/reach.test.ts:201-202`) that a naive static-import codemod will miss and that fail only at runtime.
|
||||
- The e2e harness deep-imports twice (`e2e/sdk-entry.ts:41-42`: `../src/store-registry`, `../src/accounts`); its package-name imports resolve through the exports map and are immune. `e2e/tsconfig.json` includes `"."` only — path-agnostic.
|
||||
- The e2e harness deep-imports twice (`e2e/polyfill-entry.ts:41-42`: `../src/store-registry`, `../src/accounts`); its package-name imports resolve through the exports map and are immune. `e2e/tsconfig.json` includes `"."` only — path-agnostic.
|
||||
- Decision to make alongside: whether `test/` mirrors the new folders (keeps the module↔spec correspondence at more churn) or stays flat.
|
||||
|
||||
**Silent-breakage candidates — the ones tests may not catch:**
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
# Vision & principles of the `@ng-eventually/sdk` polyfill
|
||||
# Vision & principles of `@ng-eventually/polyfill`
|
||||
|
||||
## Purpose
|
||||
|
||||
|
||||
Reference in New Issue
Block a user