diff --git a/docs/source-layout-by-fate.md b/docs/source-layout-by-fate.md new file mode 100644 index 0000000..d2b1057 --- /dev/null +++ b/docs/source-layout-by-fate.md @@ -0,0 +1,165 @@ +# Source layout by migration fate — analysis and recommendation + +**Status: analysis only.** Nothing has been moved; no source file was modified. Written 2026-08-04 from the source of `packages/client/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). + +**The question.** Today all 25 modules sit flat in `src/`, named mechanically. Three different fates coexist undistinguished: modules whose *shape* the consumer keeps (the surface the real SDK replaces), modules standing in for what the engine/verifier will do natively, and modules that exist only because the emulation runs on one shared wallet. The bet under evaluation: if the folder structure mirrors the target's own structure, divergence gets harder to commit and easier to spot. + +--- + +## 1. Is layout-by-future-layer the right axis? + +**Yes on the axis, no on the literal reading — and with a bounded claim about what it buys.** + +### 1a. The bet as literally stated is a category error + +"Mirror the target's own structure" cannot mean mirroring `nextgraph-rs`'s tree (`engine/`, `sdk/js/lib-wasm`, `sdk/js/orm`). Every line this library ships lives in the polyfill; the three references are layers we *align on*, never places we write (`README.md` § *The three references*: "REFERENCES, not places we write code"). A folder named `engine/` or `verifier/` inside `src/` would claim residency in a layer we only read — the exact conflation the README warns produces false certainty. The right axis is the target's **stack as seen from the polyfill** — what each piece of our code stands in for, and therefore what happens to it at migration — not the target's repo tree. + +### 1b. The three fates are real, but they are not three piles of equal nature + +The two that evaporate at migration differ in the only way that matters for divergence: + +- **Stand-ins for native behaviour** (cap possession and filing, the reach boundary, read filtering, inbox processing, branch registers, repo opening) have a **model to diverge from** — the engine's, level 1. This is where the dangerous failure lives: an emulation that drifts from the model teaches the consumer something to unlearn. Both incidents in `README.md` § *Design principle* happened here. +- **Shared-wallet compensation** (the account directory, the physical user, the identity relay, the diagnostics) has **nothing to align on** — NO COUNTERPART at any layer. It cannot diverge from a model; its only law is invisibility from the surface. Its failure mode is *leaking*, not drifting. + +And a fourth group the three-fate framing misses: the **target's model vocabulary** (the NURI grammar, the type guards, the branded types) — level-1-verified transcription that every layer consumes and that survives migration as knowledge rather than as code to delete. + +### 1c. Alternatives, honestly + +- **By feature** (`inbox/`, `caps/`, `read/`…): optimises "find everything about X", which the api-contract's by-subject sections already do better — and it actively hides the fate axis: `inbox.ts` would stay one folder while its sender half is target-shaped surface and its reader half is emulation detail a consumer must not code against (`docs/api-contract.md` § 9). Rejected: it organises along the axis that is already served and flattens the one that is not. +- **By dependency direction** (layered, low → high): the import graph refuses it. `docs.ts` (surface) calls `getCaps().mint` (emulation) because upstream `doc_create` itself commits `AddRepo` (level 1, `engine/verifier/src/request_processor.rs:698`, re-verified); `connect.ts` (verifier stand-in) calls `resolveAccount` (shim) because the emulation runs on the shim. These cross-fate imports are *target-faithful*, not accidents — a layout that forbids them would force artificial inversions or be violated on day one. Rejected: it encodes a property the semantics do not have. +- **Flat with naming conventions only** (`surface-docs.ts`, `shim-physical.ts`…): carries the same information at the same churn — a rename churns every importer exactly as a move does — with weaker affordances: no per-folder contract note, no one-glance grouping, and no folder-granular entry rule to grep or lint. Prefixes also rot silently in a way a misplaced file in a four-folder tree does not. Rejected as strictly dominated: same cost, less structure. +- **Do nothing — rely on the contracts:** the serious alternative. The contracts are finer-grained than any layout (per-claim epistemic labels, not per-module), and they were verified at the source. But they are read *after* the fact; the incident that motivates this analysis happened in a module whose own header states the right doctrine. A layout is confronted *during* the edit: a new module must be placed, and placing it forces the "which fate?" question at the moment the docs.ts-style mistake is made. Layout and contracts are complementary instruments — the layout is the cheap always-on prompt, the contracts remain the enforcement. + +### 1d. What the layout actually buys — bounded claims + +1. **The placement question fires at creation time.** A new module cannot be added without answering "surface, native stand-in, shared-wallet, or model?" — the question whose non-asking is the root of the flat layout's failure. +2. **The entry rule becomes folder-granular and mechanical.** Today `index.ts`'s purity is maintained per-symbol (the hand-built `store-registry-api.ts` slice). With folders the rule is "`index.ts` re-exports only from `surface/` and `model/`" — one grep, lintable in CI, reviewable at a glance. +3. **Cross-fate imports become visible seams.** `import { … } from "../emulated-verifier/…"` inside `surface/docs.ts` is a reviewable event in a diff; the same call inside a flat sibling import is invisible. The seam does not *prevent* the docs.ts incident class — it makes it show up in review. +4. **The contracts map onto the tree.** `docs/api-contract.md` ≈ `surface/` + `model/`; `docs/internal-contract.md` ≈ `emulated-verifier/` + `shared-wallet/`. Drift between doc and code becomes a folder-membership diff instead of an inventory audit. +5. **It generalises a pattern this repo already proved.** `store-registry-api.ts` (a hand-maintained surface slice), `physical.ts` (privilege as *separate functions*, not exemption flags), and the 2026-08-03 entry-header fix are all the same idea implemented piecemeal at module granularity. The layout is the same discipline promoted to the tree. + +**Verdict on the bet:** "easier to spot" — substantially yes (points 2–4). "Harder to commit" — only mildly: nothing in a folder tree stops a determined or oblivious edit, and the decisive question (*is this behaviour the target's?*) is answered by reading `nextgraph-rs`, not by any layout (§ 6). Worth doing, with the expectations of § 1d and the costs of § 5. + +--- + +## 2. The recommended layout + +Both entry files stay at `src/` root, so `package.json`'s `exports` map (exactly `.` and `./polyfill`) is untouched and the consumer application sees no change. + +| Folder | What the name asserts | Alignment reference | Fate at migration | +|---|---|---|---| +| `src/` root (`index.ts`, `polyfill.ts`) | The two published doors, nothing else. `index.ts` may re-export only from `surface/` and `model/`; `polyfill.ts` may re-export by name from anywhere — it is the polyfill-era door and its imports *are* the list of what dies. | — | `index.ts` is replaced by the real SDK via the build alias; `polyfill.ts` is deleted. | +| `model/` | The target's addressing model, transcribed: pure vocabulary (types, NURI grammar, guards). No I/O, no state, no minting. Importable by every layer. | Level 1, verified (`NuriV0`, `readcap_nuri` — `engine/repo/src/types.rs:518-521`) | Survives as knowledge; the guards stay useful against the real SDK (which takes plain strings). | +| `surface/` | App-facing, and every symbol has a target counterpart — verified or a documented bet — in `docs/api-contract.md`. A consumer coding against this folder learns nothing to unlearn. | Levels 3/2 where they answer, level-1 shape where they do not (per subject, in the contract) | Deleted when the alias flips; the consumer's code is unchanged. | +| `emulated-verifier/` | Stand-ins for what the engine/verifier/broker do natively: possession, filing, boundary, non-delivery, inbox processing, branch registers, repo opening. Aligned on the level-1 model; each module names its native counterpart mechanism. **This is the folder where divergence from the model is possible, and its main risk.** | Level 1 (the model is the specification) | Deleted — the native side takes over. | +| `shared-wallet/` | Exists only because one wallet hosts every identity. NO COUNTERPART at any layer — nothing to align on; the only law is invisibility from `surface/` and from the consumer. | None (nothing upstream has an image of this) | Evaporates entirely, replaced by nothing. | + +On the name `emulated-verifier/`: every module in it has its native counterpart running *in the verifier* (cap state and Store/User-branch replay, `Verifier::load_repo_from_read_cap` `engine/verifier/src/verifier.rs:2237`, level 1; non-delivery, `resolve_target_for_sparql` → `RepoNotFound` `engine/verifier/src/request_processor.rs:264,269`, level 1, re-verified; inbox processing, `Verifier::inbox` `verifier.rs:1674-1690`, level 1; session repos, `self.repos`). The `emulated-` prefix is load-bearing: it says *stands in for*, never *is* — the residency confusion § 1a rules out. + +--- + +## 3. Module-by-module assignment + +The 25 current modules, with the two splits' offspring shown where a module divides (§ 4 gives the criterion and the rulings). "Stays whole" means the file moves as-is. + +| Current module | Destination | What the placement asserts / notes | +|---|---|---| +| `index.ts` | `src/index.ts` (unchanged path) | The SDK-entry manifest. New rule made checkable: imports only from `surface/` and `model/`. | +| `polyfill.ts` | `src/polyfill.ts` (path unchanged) + **new** `shared-wallet/bootstrap.ts` | Split: the entry keeps the re-exports; the config store (`configure`, `getConfig`, `registryDeps`, the current-user relay, the `CapRegistry` singleton wiring) moves to `shared-wallet/bootstrap.ts` — it is the injection machinery with NO COUNTERPART by design (`docs/api-contract.md` § 1). Side effect: removes the current entry↔internal import cycles (`polyfill.ts:16` ↔ `connect.ts:37`; `polyfill.ts:228` ↔ `inbox.ts:32`). | +| `types.ts` | `model/types.ts`, minus `NgLike` / `UseShapeLike` → `shared-wallet/bootstrap.ts` | `Nuri`/`ReadCap`/`Scope` are level-1-verified vocabulary; `PrincipalId` stays with a note (target: the wallet user; polyfill: a relayed id). `NgLike`/`UseShapeLike` describe the *injection*, not the target — they belong to the bootstrap. See § 5 for the published-type wrinkle this creates. | +| `nuri.ts` | `model/nuri.ts`, minus `mintCap` → `emulated-verifier/` | The guards and `targetOf`/`parseNuri` are the model transcription. `mintCap` is the emulation's minting point — upstream only the engine mints, at repo creation (level 1, `BlockRef::readcap_nuri`, `engine/repo/src/types.rs:518-521`) — and its presence in the model module contradicts the module's own header ("nothing on the surface turns a bare reference into a cap"). P1b swaps its constant; migration deletes it. | +| `sparql.ts` | `surface/sparql.ts` | Published, generic injection-safety utilities with NO COUNTERPART and none expected (`docs/api-contract.md` § 11) — the one surface family that survives *any* migration unchanged. Placed with the surface because it is published and documented there; the folder note must carry this exception. | +| `docs.ts` | `surface/docs.ts` — stays whole | Level-2 passthroughs whose in-body cap filing and reach guard *mirror the target's own composition* (§ 4 ruling). The mint and the guard become named imports from `emulated-verifier/` — the visible seam. | +| `lifecycle.ts` | `surface/lifecycle.ts` | Pure forwarding to the injected level-2/3 calls (`docs/api-contract.md` § 2). | +| `ng-proxy.ts` | `surface/ng-proxy.ts` — stays whole | Builds the published `ng`; its two overrides compose `emulated-verifier/` predicates (the write guard stands in for `verify_perm` inside `Commit::verify`, level 1, `engine/repo/src/commit.rs:892-899`, re-verified — noting `verify` has no runtime caller today, which says nothing about the target). The `login` arm is finding F1 of the internal contract: an unprovenanced fabricated member — its fix is deletion, not relocation. | +| `use-shape.ts` | `surface/use-shape.ts` | Level-3 passthrough + the read-filter view imported from `emulated-verifier/` — mixture-by-import, already in the right shape. | +| `watch-shape.ts` | `surface/watch-shape.ts` | Surface composition over `emulated-verifier/` and the placement calls; its "planned `useShape` upgrade" header claim remains an ASSUMPTION with no provenance (`docs/api-contract.md` § 5) — a layout cannot fix that (§ 6). | +| `subscribe.ts` | `surface/subscribe.ts`, minus `subscribePhysicalDoc` → `shared-wallet/physical.ts` | The guarded `subscribeDoc`/`subscribeDocs` and `docChangeType` are surface (level 2, `doc_subscribe`, `sdk/js/lib-wasm/src/lib.rs:1908`). The physical door moves to the machinery module (§ 4); the unguarded core is exported under its `Unguarded` name for that one importer. | +| `read-model.ts` | `surface/read-model.ts` — stays whole | The anchored-read mechanics are level-1-verified and survive as composition (`docs/api-contract.md` § 6). Its possession gate and machinery filter mirror native behaviour (§ 4 ruling) and arrive via named `emulated-verifier/` imports. | +| `inbox.ts` | **split**: `surface/inbox.ts` (post, `postToDocument`, `shareCap`) + `emulated-verifier/inbox-processing.ts` (`read`/`materialize`/`readSynced`/`processInbox`/`watch`, `assertOwnInbox`, the deposit RDF vocabulary) | § 4 ruling. `surface/inbox.ts` re-exports the processing functions with a header saying exactly what `docs/api-contract.md` § 9 says — that enumerating deposits is emulation detail — so the published `inbox.*` namespace is unchanged and the warning sits at the one place the two halves meet. | +| `store-registry-api.ts` | dissolved into `surface/placement.ts` | The hand-built slice becomes a real module: the app-facing placement/addressing calls (`createEntityDoc`, `listMyEntityDocs`, `resolveScopeGraph`, `resolveWriteGraph`, `walletInbox`, `openDocumentInbox`, `documentInboxAddress`), composed from the two lower layers. Its existence today is the strongest in-repo evidence for the whole reorganisation: it does by hand what the folder boundary does structurally. | +| `store-registry.ts` | **split three ways**: `surface/placement.ts` + `emulated-verifier/branch-registers.ts` + `shared-wallet/account-registry.ts` | The sharpest case — 1377 lines spanning all three fates. `branch-registers.ts` takes the durable compartments: the Store-branch cap register (`holdOwnCap`/`readStoreCaps` — emulating `AddRepo { read_cap }`, level 1, `engine/repo/src/types.rs:1890-1899`), the User-branch Link register (`addLink`/`readLinks` — `AddLink { read_cap }`, `types.rs:1939-1948`), the inbox-cap records (`AddInboxCapV0`, `types.rs:1969-1981`) and the Header-branch address records. `account-registry.ts` takes the pointer→doc-shim indirection, `resolveAccount`/`ensureAccount`, `reservedAccount`, the cache, `AccountRecord`/`RegistrySession` — NO COUNTERPART, the shim proper. `placement.ts` (surface) keeps the app-facing calls listed above plus `userStoreDoc`/`isOwnInbox`/`myInboxes` staying internal on the register side per their contract entries. | +| `caps.ts` | `emulated-verifier/caps.ts` — stays whole (+ receives `mintCap`) | The in-memory record of what the connected holder holds — the verifier's cap state. The per-holder `heldByHolder` keying is its one shared-wallet dimension (one wallet, many holders); it stays, isolated behind the injected `holder()` and noted: at migration there is exactly one holder and the map collapses. | +| `reach.ts` | `emulated-verifier/reach.ts` | The emulated wallet boundary: stands in for "a repo you hold no cap for cannot even be addressed" (level 1, `resolve_target_for_sparql` → `RepoNotFound`, `request_processor.rs:264,269`, re-verified). The two-rules redundancy is a lib choice, documented. | +| `read-filter.ts` | `emulated-verifier/read-filter.ts` | Stands in for cryptographic non-delivery (same level-1 citations); deleted at migration with nothing to migrate to. | +| `connect.ts` | `emulated-verifier/connect.ts` — stays whole | The recipient-verifier moment (`Verifier::inbox` → `process_inbox`, level 1, `verifier.rs:1674-1690`). Its import of `resolveAccount` (shared-wallet) is a deliberate cross-fate *check* — connecting must not provision — and stays visible at the import line. | +| `open-repo.ts` | `emulated-verifier/open-repo.ts`, minus `ensurePhysicalRepoOpen` → `shared-wallet/physical.ts` | Stands in for the verifier bringing repos into `self.repos` (native at migration: open by cap at bootstrap). Flag kept from the internal contract: this module is a *current-state heal*, not a model emulation — its barrier ("TabInfo then first State") is empirical, pinned only by the e2e CONTRACT-3 probe, and its header's mechanism claim is finding F2 (contradicted at the source). The folder cannot fix either (§ 6). | +| `machinery.ts` | `emulated-verifier/machinery.ts` | The subject filter fabricates what is structurally impossible upstream: a content read cannot see Store/User/Header compartments because they are separate branches with no triples (level 1, `BranchCrdt::None`, `engine/repo/src/types.rs:1420`, re-verified). It sits beside `branch-registers.ts`, whose compartments it hides; note that `MACHINERY_NS` also covers the account-shim's vocabulary. | +| `physical.ts` | `shared-wallet/physical.ts` — grows into the complete privileged door | The quintessential shared-wallet module. It absorbs `subscribePhysicalDoc` and `ensurePhysicalRepoOpen`, so **one module is the machinery's entire unguarded API** — completing its own doctrine ("separate functions, never exemptions", `physical.ts:19-27`) at the tree level. Cost: the two unguarded cores get exported (under `Unguarded` names) from their mechanism modules; acceptable because neither entry ever re-exports them. | +| `accounts.ts` | `shared-wallet/accounts.ts` | Identity persistence for the shared wallet; NO COUNTERPART (`docs/api-contract.md` § 13); already correctly published via `/polyfill` only. | +| `access-log.ts` | `shared-wallet/access-log.ts` | Diagnoses the shared-wallet isolation leak; the identity it prefixes is the relayed virtual id. Deleted at migration. | +| `outbox-log.ts` | `shared-wallet/outbox-log.ts` | Polyfill-era trace probe over the injected SDK's private persistence (level-2 facts verified in the internal contract § 11). Deleted at migration. | + +Resulting tree: 2 entry files + `model/` (2) + `surface/` (10) + `emulated-verifier/` (8) + `shared-wallet/` (6). + +--- + +## 4. The mixed modules — mechanical detection, one criterion, and rulings + +### Detection method + +Fate labels exist per *export* in the two contracts; a module is mixed when its exports (or its internal effects) span fates. Three code signals find the internal effects mechanically, without trusting headers: + +- **Signal A — wire call × emulation-state write:** the module calls the injected `ng` (`getConfig().ng`) *and* mutates emulation state (`getCaps().mint/learn/open`, `addLink`, `declareInfrastructure`). Grep hits: `docs.ts:73` (mint), `inbox.ts:385` (learn), `store-registry.ts:875,893-895,903,1169,1371` (learn/open), `connect.ts:67` (learn). +- **Signal B — guarded/unguarded twin exports:** the `*Physical*` / `*Unguarded` pairs. Hits: `subscribe.ts:104/118`, `open-repo.ts:167/184`. +- **Signal C — act vs stand-in-processing under one namespace:** exports of the same module carrying different fate labels in the contracts. Hits: `inbox.ts` (§ 9: sender acts are target-shaped; deposit enumeration is emulation detail), `store-registry.ts` (§ 12: labels range from level-2 VERIFIED to NO COUNTERPART), `types.ts` (model types vs injection types, § 1 vs § 10), `nuri.ts` (guards vs `mintCap`, internal contract § 2), `polyfill.ts` (entry vs config store). + +Full mixed list: `docs.ts`, `read-model.ts`, `inbox.ts`, `subscribe.ts`, `open-repo.ts`, `store-registry.ts`, `nuri.ts`, `types.ts`, `polyfill.ts`, `ng-proxy.ts`, `caps.ts`, `connect.ts`. (`use-shape.ts` and `watch-shape.ts` cross fates only through imports — already the desired end state.) + +### The criterion + +**Split when the halves have different fates AND different callers. Keep whole when the mixture reproduces a composition the target itself performs atomically — and then express the emulated half as a named import from the other folder, so the seam is on the import line.** Corollary: an unguarded twin of a guarded operation always lives with the machinery that calls it, never beside its guarded sibling. + +### Rulings + +- **`docs.ts` — KEEP WHOLE.** The cap filing inside `docCreate` mirrors the target's own `doc_create`, which commits `AddRepo` to the Store branch and `ldp:contains` to the Main branch *in the same native call* (level 1, `engine/verifier/src/request_processor.rs:697-710`, re-verified). Splitting the mint into a separate caller-visible step would create a two-step creation surface the target does not have — the split itself would be the divergence. Same for the reach guard: the refusal is native (`RepoNotFound`). The fix is visibility, not surgery: both effects become named imports from `emulated-verifier/`. +- **`read-model.ts` — KEEP WHOLE.** Its possession gate mirrors native non-delivery, and its machinery-subject drop mirrors the structural invisibility of non-content branches (`BranchCrdt::None`, `types.rs:1420`, level 1). Both are the target's own composition of "read a document". +- **`inbox.ts` — SPLIT.** Different fates (api-contract § 9: the acts are target-shaped level-1 inventions; the deposit-list surface "may never have this shape") *and* different callers (apps post/share/watch; `connect.ts` processes). The deposit RDF vocabulary — pure emulation transport (upstream a deposit is a sealed message, `InboxMsgBody`, `engine/net/src/types.rs:4265`, level 1, carrying no target document) — lives once, on the emulated side. +- **`subscribe.ts` / `open-repo.ts` — SPLIT the physical doors out** (criterion's corollary): different caller (machinery only), different fate (the guarded/unguarded pair collapses to one call when the wallet is the boundary). They regroup in `shared-wallet/physical.ts`. +- **`store-registry.ts` — SPLIT three ways** (§ 3). It is the module the flat layout hides most: signal C fires on nearly every export group, and the repo already voted for the split by hand-building `store-registry-api.ts`. +- **`nuri.ts` — SPLIT `mintCap` out.** Different fate (model vocabulary survives; the minting point is deleted when the engine mints) and the module's own stated invariant argues for it. +- **`types.ts` — SPLIT the injection types out** (small; see § 5 for the published-type consequence, which must be decided, not slipped). +- **`polyfill.ts` — SPLIT entry from config store.** Different fates (a published door vs internal state) and it removes real import cycles. +- **`ng-proxy.ts` — KEEP WHOLE.** 59 lines; the overrides *are* "what the native side takes over" and already compose `emulated-verifier/` predicates; the proxy artifact itself is the published surface. F1 (the fabricated `login` member) is fixed by deletion wherever the file lives. +- **`caps.ts` — KEEP WHOLE.** The per-holder keying is shared-wallet-flavoured, but splitting holder-resolution from the possession model would fragment one coherent level-1 model for no boundary gain; the injected `holder()` already isolates the dimension that collapses at migration. +- **`connect.ts` — KEEP WHOLE.** Its shim import is a deliberate cross-fate check (must-not-provision), which is exactly what the seam should show. + +--- + +## 5. Cost and risk + +**What does not change: the published surface.** Both entries keep their `src/` paths; `package.json`'s `exports` map is untouched; the `inbox.*` and `storeRegistry.*` namespaces are re-assembled at the entries with identical contents. A consumer application importing the two entries sees nothing — with one deliberate exception below. + +**Import churn — the inventory:** + +- 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. +- 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. **Module-evaluation order.** `polyfill.ts` is today both entry and config store and sits inside import cycles (`polyfill ↔ connect`, `polyfill ↔ inbox`) that work through ES-module hoisting; the `CapRegistry` singleton is constructed at module scope (`polyfill.ts:97`). Re-cutting the graph changes which module evaluates first; a cycle that works today can break — or, worse, *change initialization order without breaking*. Mitigation: extract `shared-wallet/bootstrap.ts` as its own first step with the full unit + e2e suite run before any other move (baseline discipline), since the e2e suite is what actually exercises load order against a real broker. +2. **`export * from "./types"`.** After the types split, `NgLike`/`UseShapeLike` silently vanish from the `.` entry's type surface — erased types, so nothing in this repo's runtime tests notices; only the consumer's typecheck would. This is a real (if arguably desirable) published-surface change and must be an explicit decision: either re-export them deliberately from `/polyfill` (where `EventuallyConfig`, which references them, already lives) with a documented deprecation on `.`, or accept the narrowing and record it in the api-contract. +3. **The contracts' citations — the largest single cost.** `docs/api-contract.md` and `docs/internal-contract.md` (plus several briefs) carry hundreds of `file:line` references into `src/`; every moved or split module stales them wholesale. These two documents are the library's enforcement instrument — letting their citations rot would undercut the very discipline the reorganisation serves. A citation-refresh pass over both contracts is part of the change, not a follow-up. +4. **`git blame` archaeology.** Moves (and especially the three-way `store-registry` split) break naive blame; `--follow` works per-file but split hunks lose lineage. One-time tax; worth staging the splits as move-then-edit commits so content moves stay detectable. + +**Staging that contains the risk:** (1) extract `shared-wallet/bootstrap.ts`, full suite green; (2) pure moves into the four folders, no content edits, full suite green; (3) the splits (`inbox`, `store-registry`, `nuri`, `types`), one per commit, each behind its baseline; (4) the citation-refresh pass on both contracts. Each stage leaves the published surface byte-identical (stage-3 exception 2 above being the one flagged decision). + +--- + +## 6. What the layout will NOT fix + +Being specific, because overclaiming here would recreate the false-certainty problem the layout is meant to reduce: + +- **It cannot decide whether a behaviour is the target's.** The decisive act remains reading `nextgraph-rs`. Both README § *Design principle* incidents would have type-checked and folder-checked: "every document has a native inbox" was a *belief* error, and the owner-inbox pointer was a *model* error inside code that belongs exactly where it was. A correctly named folder holds wrong code without complaint. +- **It cannot fix wrong claims inside correctly placed modules.** Finding F2 (`open-repo.ts`'s header asserts a silent-0-rows mechanism the source contradicts — upstream errors `RepoNotFound`, `request_processor.rs:264,269`, level 1) and `watch-shape.ts`'s unprovenanced "planned upgrade" survive any tree untouched. Header claims are policed by source-verification passes, not placement. +- **It is coarser than the epistemic labels.** PASSTHROUGH vs LEVEL-1 SHAPE vs ASSUMPTION vary per *claim* within one module (`inbox.post`'s act is level-1-shaped, its transport is pure emulation, arbitrary payloads are an ASSUMPTION — all in one function's orbit). A folder carries one label; the contracts remain the finer instrument and the layout must not be read as replacing them. +- **It cannot stop in-module shape drift.** `Deposit` growing a target-document field — the exact divergence class of the reverted owner-inbox episode, since upstream a message carries no document (`InboxMsgBody`, `engine/net/src/types.rs:4265`, level 1) — is one line in a correctly placed file. +- **It does not police the empirical bets.** The sync barrier's push ordering and "a held subscription keeps the repo open" are pinned by the e2e CONTRACT-3 probe alone; no layout substitutes for that tripwire. +- **It does not remove dead or decorative surface** (`inbox.watch`'s ignored `intervalMs`, the decorative write caps) — inventory work, already tracked in the contracts. +- **A wrong placement is worse than no placement.** Folders assert; a mis-filed module borrows the folder's authority (a shim-flavoured helper landing in `surface/` would *look* migration-safe). The contracts' per-subject verification remains the check on the layout — never the reverse. + +--- + +*Cross-references: `README.md` § Design principle and § The three references (the doctrine this layout serializes into the tree); `docs/api-contract.md` (the would-be `surface/`+`model/` inventory); `docs/internal-contract.md` (the would-be `emulated-verifier/`+`shared-wallet/` inventory, findings F1–F5).* diff --git a/packages/client/e2e/sdk-entry.ts b/packages/client/e2e/sdk-entry.ts index ecf7173..c9b6ace 100644 --- a/packages/client/e2e/sdk-entry.ts +++ b/packages/client/e2e/sdk-entry.ts @@ -38,8 +38,8 @@ import { // The harness tests the LIBRARY, so it legitimately reaches machinery a consumer // application must not — but through the internal path, never the published entry. // `storeRegistry` above is the app-facing slice; these are the shim internals. -import * as registryInternals from "../src/store-registry"; -import * as accounts from "../src/accounts"; +import * as registryInternals from "../src/shared-wallet/account-registry"; +import * as accounts from "../src/shared-wallet/accounts"; import { isNuri } from "@ng-eventually/client"; import type { Nuri, ShapeObservable, ShapeQuery } from "@ng-eventually/client"; diff --git a/packages/client/src/caps.ts b/packages/client/src/emulated-verifier/caps.ts similarity index 98% rename from packages/client/src/caps.ts rename to packages/client/src/emulated-verifier/caps.ts index 1db92e7..2b6bf4c 100644 --- a/packages/client/src/caps.ts +++ b/packages/client/src/emulated-verifier/caps.ts @@ -50,8 +50,8 @@ * writer; they are left as-is and belong to P1b. */ -import { hasReadCap, mintCap, targetOf } from "./nuri"; -import type { Nuri, PrincipalId, ReadCap, Scope } from "./types"; +import { hasReadCap, mintCap, targetOf } from "../model/nuri"; +import type { Nuri, PrincipalId, ReadCap, Scope } from "../model/types"; /** The map key of the anonymous holder (no identity established yet). */ const ANONYMOUS = ""; diff --git a/packages/client/src/connect.ts b/packages/client/src/emulated-verifier/connect.ts similarity index 96% rename from packages/client/src/connect.ts rename to packages/client/src/emulated-verifier/connect.ts index 8a82112..98ace7b 100644 --- a/packages/client/src/connect.ts +++ b/packages/client/src/emulated-verifier/connect.ts @@ -34,9 +34,9 @@ * them and this drains each in turn. */ -import { getCaps, getCurrentUser } from "./polyfill"; -import { myInboxes, readLinks, resolveAccount } from "./store-registry"; -import { processInbox } from "./inbox"; +import { getCaps, getCurrentUser } from "../polyfill"; +import { myInboxes, readLinks, resolveAccount } from "../shared-wallet/account-registry"; +import { processInbox } from "../surface/inbox"; /** The in-flight connection work, per user key — so two calls do not race. */ const inFlight = new Map>(); diff --git a/packages/client/src/machinery.ts b/packages/client/src/emulated-verifier/machinery.ts similarity index 100% rename from packages/client/src/machinery.ts rename to packages/client/src/emulated-verifier/machinery.ts diff --git a/packages/client/src/open-repo.ts b/packages/client/src/emulated-verifier/open-repo.ts similarity index 98% rename from packages/client/src/open-repo.ts rename to packages/client/src/emulated-verifier/open-repo.ts index 80114f3..f68c9ff 100644 --- a/packages/client/src/open-repo.ts +++ b/packages/client/src/emulated-verifier/open-repo.ts @@ -60,10 +60,10 @@ */ import { mustNotAttempt } from "./reach"; -import { getConfig, getStoreRegistryDeps } from "./polyfill"; -import { subscribePhysicalDoc, type Unsubscribe } from "./subscribe"; -import { logStage, shortNuri } from "./access-log"; -import type { Nuri } from "./types"; +import { getConfig, getStoreRegistryDeps } from "../polyfill"; +import { subscribePhysicalDoc, type Unsubscribe } from "../surface/subscribe"; +import { logStage, shortNuri } from "../shared-wallet/access-log"; +import type { Nuri } from "../model/types"; /** * The per-nuri bootstrap sync state (lib-internal). See the module header: diff --git a/packages/client/src/reach.ts b/packages/client/src/emulated-verifier/reach.ts similarity index 98% rename from packages/client/src/reach.ts rename to packages/client/src/emulated-verifier/reach.ts index 39cfe21..d7d0e72 100644 --- a/packages/client/src/reach.ts +++ b/packages/client/src/emulated-verifier/reach.ts @@ -40,9 +40,9 @@ * At migration this module disappears: the boundary becomes the wallet itself. */ -import { getCaps } from "./polyfill"; -import { targetOf } from "./nuri"; -import type { Nuri } from "./types"; +import { getCaps } from "../polyfill"; +import { targetOf } from "../model/nuri"; +import type { Nuri } from "../model/types"; /** * NURIs of the polyfill's own scaffolding, registered as they are resolved. diff --git a/packages/client/src/read-filter.ts b/packages/client/src/emulated-verifier/read-filter.ts similarity index 97% rename from packages/client/src/read-filter.ts rename to packages/client/src/emulated-verifier/read-filter.ts index 42169d8..fd5b930 100644 --- a/packages/client/src/read-filter.ts +++ b/packages/client/src/emulated-verifier/read-filter.ts @@ -21,8 +21,8 @@ */ import type { CapRegistry } from "./caps"; -import { isNuri } from "./nuri"; -import type { Nuri } from "./types"; +import { isNuri } from "../model/nuri"; +import type { Nuri } from "../model/types"; /** The document (repo NURI) an item lives in — its `@graph`. The ORM boundary: * `@graph` is an untyped value on a property bag, so it is narrowed here rather diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index 3fb255b..62b9061 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -20,24 +20,24 @@ * away is visible at the import line. */ -export * from "./types"; -export { useShape } from "./use-shape"; -export { watchShape } from "./watch-shape"; -export type { ShapeQuery, ShapeObservable } from "./watch-shape"; -export { init, initNg } from "./lifecycle"; -export * as inbox from "./inbox"; -export * as docs from "./docs"; -export { subscribeDoc, subscribeDocs, docChangeType } from "./subscribe"; -export type { DocChange, DocChangeType, Unsubscribe } from "./subscribe"; -export * as readModel from "./read-model"; -export type { UnionSubject } from "./read-model"; -export * as storeRegistry from "./store-registry-api"; +export * from "./model/types"; +export { useShape } from "./surface/use-shape"; +export { watchShape } from "./surface/watch-shape"; +export type { ShapeQuery, ShapeObservable } from "./surface/watch-shape"; +export { init, initNg } from "./surface/lifecycle"; +export * as inbox from "./surface/inbox"; +export * as docs from "./surface/docs"; +export { subscribeDoc, subscribeDocs, docChangeType } from "./surface/subscribe"; +export type { DocChange, DocChangeType, Unsubscribe } from "./surface/subscribe"; +export * as readModel from "./surface/read-model"; +export type { UnionSubject } from "./surface/read-model"; +export * as storeRegistry from "./surface/placement"; // SPARQL injection-safety helpers — so the app can reuse the same escaping / // validation when it builds SPARQL by interpolation. `escapeLiteral` for string // literals, `escapeIri` to embed untrusted values in an IRI, `assertNuri` to // validate trusted-shaped NURIs before embedding them in an IRI. -export { escapeLiteral, escapeIri, assertNuri } from "./sparql"; +export { escapeLiteral, escapeIri, assertNuri } from "./surface/sparql"; // NURI type guards — the doors through which an app's own `string` (read back // from storage, a URL, JSON, a form) becomes a typed `Nuri` or `ReadCap`. `Nuri` @@ -46,7 +46,7 @@ export { escapeLiteral, escapeIri, assertNuri } from "./sparql"; // particular, it cannot pass a bare reference where a cap is required. Narrow // with these rather than casting: a cast re-opens exactly the confusion the // types exist to close. -export { isNuri, hasReadCap } from "./nuri"; +export { isNuri, hasReadCap } from "./model/nuri"; // SDK type re-exports — so the app imports these from @ng-eventually/client too, // not from @ng-org. `export type` is ERASED at build, so this adds NO runtime @@ -55,7 +55,7 @@ export type { ShapeType, BaseType, Schema } from "@ng-org/shex-orm"; export type { DeepSignalSet } from "@ng-org/alien-deepsignals"; export type { NG } from "@ng-org/web"; -import { makeNg } from "./ng-proxy"; +import { makeNg } from "./surface/ng-proxy"; /** SDK-identical `ng` (wrapped). Drop-in replacement for `@ng-org/web`'s `ng`. */ export const ng: Record = makeNg(); diff --git a/packages/client/src/nuri.ts b/packages/client/src/model/nuri.ts similarity index 100% rename from packages/client/src/nuri.ts rename to packages/client/src/model/nuri.ts diff --git a/packages/client/src/types.ts b/packages/client/src/model/types.ts similarity index 100% rename from packages/client/src/types.ts rename to packages/client/src/model/types.ts diff --git a/packages/client/src/polyfill.ts b/packages/client/src/polyfill.ts index c8ea350..90b08ef 100644 --- a/packages/client/src/polyfill.ts +++ b/packages/client/src/polyfill.ts @@ -8,12 +8,12 @@ * here is removed at migration. */ -import type { NgLike, UseShapeLike, Nuri, PrincipalId, ReadCap } from "./types"; -import type { RegistrySession } from "./store-registry"; -import { CapRegistry } from "./caps"; -import { setAccessLog } from "./access-log"; -import { inspectOutbox } from "./outbox-log"; -import { startConnect } from "./connect"; +import type { NgLike, UseShapeLike, Nuri, PrincipalId, ReadCap } from "./model/types"; +import type { RegistrySession } from "./shared-wallet/account-registry"; +import { CapRegistry } from "./emulated-verifier/caps"; +import { setAccessLog } from "./shared-wallet/access-log"; +import { inspectOutbox } from "./shared-wallet/outbox-log"; +import { startConnect } from "./emulated-verifier/connect"; /** * Consumer-injected dependencies of the storeRegistry (polyfill-era). The @@ -224,9 +224,9 @@ export function resetCaps(): void { // lives in `inbox.ts` because sharing IS an inbox deposit (upstream: a sealed // message carrying the cap), but it is surfaced here so the cap vocabulary stays // on the polyfill side of the boundary rather than in the SDK-identical entry. -export { CapRegistry } from "./caps"; -export { shareCap } from "./inbox"; -export { connectedUser } from "./connect"; +export { CapRegistry } from "./emulated-verifier/caps"; +export { shareCap } from "./surface/inbox"; +export { connectedUser } from "./emulated-verifier/connect"; // --- identity persistence (polyfill-era, no SDK counterpart) ---------------- // @@ -235,7 +235,7 @@ export { connectedUser } from "./connect"; // one shared wallet hosts several identities. The real SDK has no counterpart: there // each user opens their own wallet, and "who am I" is the session. Shipping it from // the SDK entry advertised as durable something that disappears at migration. -export * as accounts from "./accounts"; -export type { AccountStorage } from "./accounts"; +export * as accounts from "./shared-wallet/accounts"; +export type { AccountStorage } from "./shared-wallet/accounts"; // Config-shaped types the bootstrap needs; both describe the shim, not the SDK. -export type { AccountRecord, RegistrySession } from "./store-registry"; +export type { AccountRecord, RegistrySession } from "./shared-wallet/account-registry"; diff --git a/packages/client/src/access-log.ts b/packages/client/src/shared-wallet/access-log.ts similarity index 99% rename from packages/client/src/access-log.ts rename to packages/client/src/shared-wallet/access-log.ts index cae81a7..edccd45 100644 --- a/packages/client/src/access-log.ts +++ b/packages/client/src/shared-wallet/access-log.ts @@ -19,7 +19,7 @@ * migration where the broker/verifier enforces isolation natively. */ -import { getCurrentUser } from "./polyfill"; +import { getCurrentUser } from "../polyfill"; /** Access kind: a document READ or a document WRITE. */ export type AccessOp = "READ" | "WRITE"; diff --git a/packages/client/src/store-registry.ts b/packages/client/src/shared-wallet/account-registry.ts similarity index 99% rename from packages/client/src/store-registry.ts rename to packages/client/src/shared-wallet/account-registry.ts index b570e2a..38f61ce 100644 --- a/packages/client/src/store-registry.ts +++ b/packages/client/src/shared-wallet/account-registry.ts @@ -61,15 +61,15 @@ * `ng`), so this module imports **no** `@ng-org` package. */ -import { sparqlUpdate, sparqlQuery } from "./docs"; +import { sparqlUpdate, sparqlQuery } from "../surface/docs"; import { physicalCreate, physicalQuery, physicalUpdate } from "./physical"; -import { getCaps, getCurrentUser, getStoreRegistryDeps } from "./polyfill"; -import { ensureRepoOpen, ensurePhysicalRepoOpen } from "./open-repo"; -import { escapeLiteral, escapeIri, assertNuri } from "./sparql"; -import { hasReadCap, isNuri, mintCap } from "./nuri"; -import { mustNotAttempt } from "./reach"; +import { getCaps, getCurrentUser, getStoreRegistryDeps } from "../polyfill"; +import { ensureRepoOpen, ensurePhysicalRepoOpen } from "../emulated-verifier/open-repo"; +import { escapeLiteral, escapeIri, assertNuri } from "../surface/sparql"; +import { hasReadCap, isNuri, mintCap } from "../model/nuri"; +import { mustNotAttempt } from "../emulated-verifier/reach"; import { accessLogPrefix, logStage, shortNuri } from "./access-log"; -import type { Nuri, ReadCap, Scope } from "./types"; +import type { Nuri, ReadCap, Scope } from "../model/types"; // --- sharedWalletShim model ---------------------------------------------- diff --git a/packages/client/src/accounts.ts b/packages/client/src/shared-wallet/accounts.ts similarity index 100% rename from packages/client/src/accounts.ts rename to packages/client/src/shared-wallet/accounts.ts diff --git a/packages/client/src/outbox-log.ts b/packages/client/src/shared-wallet/outbox-log.ts similarity index 100% rename from packages/client/src/outbox-log.ts rename to packages/client/src/shared-wallet/outbox-log.ts diff --git a/packages/client/src/physical.ts b/packages/client/src/shared-wallet/physical.ts similarity index 97% rename from packages/client/src/physical.ts rename to packages/client/src/shared-wallet/physical.ts index 01bb9b4..40ed302 100644 --- a/packages/client/src/physical.ts +++ b/packages/client/src/shared-wallet/physical.ts @@ -38,10 +38,10 @@ * split once each user opens their own wallet. */ -import { getConfig } from "./polyfill"; +import { getConfig } from "../polyfill"; import { logAccess } from "./access-log"; -import { isNuri } from "./nuri"; -import type { Nuri } from "./types"; +import { isNuri } from "../model/nuri"; +import type { Nuri } from "../model/types"; /** * Create a document as the PHYSICAL user — the shim's own documents (the doc-shim, diff --git a/packages/client/src/docs.ts b/packages/client/src/surface/docs.ts similarity index 96% rename from packages/client/src/docs.ts rename to packages/client/src/surface/docs.ts index d3c9d57..093a3c8 100644 --- a/packages/client/src/docs.ts +++ b/packages/client/src/surface/docs.ts @@ -13,11 +13,11 @@ * app's storeRegistry usage), so this is a drop-in for those raw calls. */ -import { getCaps, getConfig } from "./polyfill"; -import { logAccess, enabled as accessLogEnabled } from "./access-log"; -import { isNuri } from "./nuri"; -import { assertMayReach } from "./reach"; -import type { Nuri } from "./types"; +import { getCaps, getConfig } from "../polyfill"; +import { logAccess, enabled as accessLogEnabled } from "../shared-wallet/access-log"; +import { isNuri } from "../model/nuri"; +import { assertMayReach } from "../emulated-verifier/reach"; +import type { Nuri } from "../model/types"; // The low common point for ALL document access: every read in the SDK routes // through `sparqlQuery`, every write through `sparqlUpdate` (+ container creation diff --git a/packages/client/src/inbox.ts b/packages/client/src/surface/inbox.ts similarity index 98% rename from packages/client/src/inbox.ts rename to packages/client/src/surface/inbox.ts index e658649..7886b9b 100644 --- a/packages/client/src/inbox.ts +++ b/packages/client/src/surface/inbox.ts @@ -28,19 +28,19 @@ import { depositInto, sparqlQuery } from "./docs"; import { subscribeDoc } from "./subscribe"; -import { ensureRepoOpen } from "./open-repo"; -import { getCaps, getCurrentUser, getStoreRegistryDeps } from "./polyfill"; -import { addLink, documentInboxAddress, isOwnInbox } from "./store-registry"; +import { ensureRepoOpen } from "../emulated-verifier/open-repo"; +import { getCaps, getCurrentUser, getStoreRegistryDeps } from "../polyfill"; +import { addLink, documentInboxAddress, isOwnInbox } from "../shared-wallet/account-registry"; import { escapeLiteral } from "./sparql"; -import { hasReadCap } from "./nuri"; +import { hasReadCap } from "../model/nuri"; import { accessLogPrefix, enabled as accessLogEnabled, logAccess, logStage, shortNuri, -} from "./access-log"; -import type { Nuri, PrincipalId, ReadCap } from "./types"; +} from "../shared-wallet/access-log"; +import type { Nuri, PrincipalId, ReadCap } from "../model/types"; // --- deposit model -------------------------------------------------------- diff --git a/packages/client/src/lifecycle.ts b/packages/client/src/surface/lifecycle.ts similarity index 95% rename from packages/client/src/lifecycle.ts rename to packages/client/src/surface/lifecycle.ts index d11d295..b76df4f 100644 --- a/packages/client/src/lifecycle.ts +++ b/packages/client/src/surface/lifecycle.ts @@ -5,7 +5,7 @@ * a hook point later (e.g. opening the shared wallet on `init`). */ -import { getConfig } from "./polyfill"; +import { getConfig } from "../polyfill"; /** Forwards to the real `@ng-org/web` `init`. */ export function init(...args: any[]): any { diff --git a/packages/client/src/ng-proxy.ts b/packages/client/src/surface/ng-proxy.ts similarity index 95% rename from packages/client/src/ng-proxy.ts rename to packages/client/src/surface/ng-proxy.ts index ae39889..158a936 100644 --- a/packages/client/src/ng-proxy.ts +++ b/packages/client/src/surface/ng-proxy.ts @@ -4,8 +4,8 @@ * surface stays identical to `@ng-org/web`'s `ng`. */ -import { getConfig, getCaps, getCurrentUser } from "./polyfill"; -import type { Nuri } from "./types"; +import { getConfig, getCaps, getCurrentUser } from "../polyfill"; +import type { Nuri } from "../model/types"; export function makeNg(): Record { return new Proxy({} as Record, { diff --git a/packages/client/src/store-registry-api.ts b/packages/client/src/surface/placement.ts similarity index 92% rename from packages/client/src/store-registry-api.ts rename to packages/client/src/surface/placement.ts index aa29d9a..064becd 100644 --- a/packages/client/src/store-registry-api.ts +++ b/packages/client/src/surface/placement.ts @@ -5,7 +5,7 @@ * placement/addressing calls a consumer application legitimately makes, and the * shim machinery that makes virtual users work at all (account resolution, the * durable cap registers, the inbox-ownership predicate, cache resets). Until now - * `index.ts` did `export * as storeRegistry from "./store-registry"` and shipped + * `index.ts` did `export * as storeRegistry from "../shared-wallet/account-registry"` and shipped * both, so an application could reach `ensureAccount`, `addLink` or * `resetRegistryCache` from the SDK-identical entry — machinery it must never call, * on the entry whose whole promise is "this survives migration unchanged". @@ -34,4 +34,4 @@ export { openDocumentInbox, /** WHERE to deposit for a document — readable by any holder of it. `undefined` if none. */ documentInboxAddress, -} from "./store-registry"; +} from "../shared-wallet/account-registry"; diff --git a/packages/client/src/read-model.ts b/packages/client/src/surface/read-model.ts similarity index 96% rename from packages/client/src/read-model.ts rename to packages/client/src/surface/read-model.ts index 99c9aeb..22b2173 100644 --- a/packages/client/src/read-model.ts +++ b/packages/client/src/surface/read-model.ts @@ -42,12 +42,12 @@ */ import { docCreate, sparqlUpdate, sparqlQuery } from "./docs"; -import { getCaps, getStoreRegistryDeps } from "./polyfill"; -import { mustNotAttempt } from "./reach"; -import { ensureReposOpen } from "./open-repo"; +import { getCaps, getStoreRegistryDeps } from "../polyfill"; +import { mustNotAttempt } from "../emulated-verifier/reach"; +import { ensureReposOpen } from "../emulated-verifier/open-repo"; import { assertNuri } from "./sparql"; -import { isMachinerySubject } from "./machinery"; -import type { Nuri } from "./types"; +import { isMachinerySubject } from "../emulated-verifier/machinery"; +import type { Nuri } from "../model/types"; // Keep the primitives referenced so tree-shaking never drops the import used by // the (side-effecting) open step below; `docCreate`/`sparqlUpdate` are not used diff --git a/packages/client/src/sparql.ts b/packages/client/src/surface/sparql.ts similarity index 100% rename from packages/client/src/sparql.ts rename to packages/client/src/surface/sparql.ts diff --git a/packages/client/src/subscribe.ts b/packages/client/src/surface/subscribe.ts similarity index 98% rename from packages/client/src/subscribe.ts rename to packages/client/src/surface/subscribe.ts index 8285807..deaaf51 100644 --- a/packages/client/src/subscribe.ts +++ b/packages/client/src/surface/subscribe.ts @@ -33,9 +33,9 @@ * builds a set of these with per-doc error isolation to preserve that property. */ -import { getConfig, getStoreRegistryDeps } from "./polyfill"; -import { assertMayReach } from "./reach"; -import type { Nuri } from "./types"; +import { getConfig, getStoreRegistryDeps } from "../polyfill"; +import { assertMayReach } from "../emulated-verifier/reach"; +import type { Nuri } from "../model/types"; /** * A push from the platform to a document subscriber. Loosely typed: the raw diff --git a/packages/client/src/use-shape.ts b/packages/client/src/surface/use-shape.ts similarity index 84% rename from packages/client/src/use-shape.ts rename to packages/client/src/surface/use-shape.ts index 946a06d..a82de6d 100644 --- a/packages/client/src/use-shape.ts +++ b/packages/client/src/surface/use-shape.ts @@ -6,8 +6,8 @@ * only delivers documents whose cap the wallet holds. */ -import { getConfig, getCaps } from "./polyfill"; -import { makeReadFilteredView } from "./read-filter"; +import { getConfig, getCaps } from "../polyfill"; +import { makeReadFilteredView } from "../emulated-verifier/read-filter"; export function useShape(shapeType: unknown, scope: unknown): unknown { const set = getConfig().useShape(shapeType, scope) as object; diff --git a/packages/client/src/watch-shape.ts b/packages/client/src/surface/watch-shape.ts similarity index 98% rename from packages/client/src/watch-shape.ts rename to packages/client/src/surface/watch-shape.ts index 1b3f460..73fe26d 100644 --- a/packages/client/src/watch-shape.ts +++ b/packages/client/src/surface/watch-shape.ts @@ -51,12 +51,12 @@ * `isError` fires ONLY on a real thrown exception in the pipeline. */ -import { getCaps, getCurrentUser } from "./polyfill"; -import { ensureReposOpen, getSyncState } from "./open-repo"; +import { getCaps, getCurrentUser } from "../polyfill"; +import { ensureReposOpen, getSyncState } from "../emulated-verifier/open-repo"; import { readUnion, type UnionSubject } from "./read-model"; import { subscribeDoc, type Unsubscribe } from "./subscribe"; -import { listMyEntityDocs, userStoreDoc } from "./store-registry"; -import type { Nuri, Scope } from "./types"; +import { listMyEntityDocs, userStoreDoc } from "../shared-wallet/account-registry"; +import type { Nuri, Scope } from "../model/types"; /** * The RDF `type` predicate IRI. A SHEX shape pins its class via a triple diff --git a/packages/client/test/access-log.test.ts b/packages/client/test/access-log.test.ts index 1eb1a3e..5461d1c 100644 --- a/packages/client/test/access-log.test.ts +++ b/packages/client/test/access-log.test.ts @@ -19,8 +19,8 @@ */ import { describe, it, expect, mock, beforeEach, afterEach, afterAll } from "bun:test"; -import { setAccessLog, enabled, shortNuri } from "../src/access-log"; -import { docCreate, sparqlUpdate, sparqlQuery } from "../src/docs"; +import { setAccessLog, enabled, shortNuri } from "../src/shared-wallet/access-log"; +import { docCreate, sparqlUpdate, sparqlQuery } from "../src/surface/docs"; import { configure, configureStoreRegistry, diff --git a/packages/client/test/accounts.test.ts b/packages/client/test/accounts.test.ts index d2a4acd..04a44d9 100644 --- a/packages/client/test/accounts.test.ts +++ b/packages/client/test/accounts.test.ts @@ -4,7 +4,7 @@ import { browserIdentityStore, ACCOUNT_STORAGE_KEY, type AccountStorage, -} from "../src/accounts"; +} from "../src/shared-wallet/accounts"; // In-memory fake of the Storage subset — keeps this framework/DOM-agnostic. function fakeStorage(): AccountStorage & { map: Map } { diff --git a/packages/client/test/anti-fork.test.ts b/packages/client/test/anti-fork.test.ts index 047d53b..383de03 100644 --- a/packages/client/test/anti-fork.test.ts +++ b/packages/client/test/anti-fork.test.ts @@ -24,15 +24,15 @@ import { ensureAccount, resolveAccount, resetRegistryCache, -} from "../src/store-registry"; -import type { RegistrySession } from "../src/store-registry"; +} from "../src/shared-wallet/account-registry"; +import type { RegistrySession } from "../src/shared-wallet/account-registry"; import { configure, configureStoreRegistry, resetStoreRegistry, resetConfig, } from "../src/polyfill"; -import { resetOpenedRepos } from "../src/open-repo"; +import { resetOpenedRepos } from "../src/emulated-verifier/open-repo"; afterAll(() => { resetConfig(); diff --git a/packages/client/test/caps.test.ts b/packages/client/test/caps.test.ts index 06e44bf..6009aa8 100644 --- a/packages/client/test/caps.test.ts +++ b/packages/client/test/caps.test.ts @@ -7,9 +7,9 @@ * function turns a bare reference into a cap. */ import { test, expect } from "bun:test"; -import { CapRegistry } from "../src/caps"; -import { hasReadCap, targetOf } from "../src/nuri"; -import type { ReadCap } from "../src/types"; +import { CapRegistry } from "../src/emulated-verifier/caps"; +import { hasReadCap, targetOf } from "../src/model/nuri"; +import type { ReadCap } from "../src/model/types"; /** A registry whose holder the test drives. */ function registry(initial: string | null = "alice") { diff --git a/packages/client/test/cold-start-anchor.test.ts b/packages/client/test/cold-start-anchor.test.ts index 3f4e5a9..c20a6d1 100644 --- a/packages/client/test/cold-start-anchor.test.ts +++ b/packages/client/test/cold-start-anchor.test.ts @@ -21,8 +21,8 @@ */ import { describe, it, expect, mock, afterAll, beforeEach } from "bun:test"; -import { ensureAccount, resolveWriteGraph, resetRegistryCache } from "../src/store-registry"; -import { resetOpenedRepos } from "../src/open-repo"; +import { ensureAccount, resolveWriteGraph, resetRegistryCache } from "../src/shared-wallet/account-registry"; +import { resetOpenedRepos } from "../src/emulated-verifier/open-repo"; import { configure, configureStoreRegistry, @@ -31,7 +31,7 @@ import { resetCaps, setCurrentUser, } from "../src/polyfill"; -import { resetInfrastructure } from "../src/reach"; +import { resetInfrastructure } from "../src/emulated-verifier/reach"; const SESSION = { sessionId: "sid-cold", privateStoreId: "PRIV-COLD" }; const ANCHOR = `did:ng:${SESSION.privateStoreId}`; diff --git a/packages/client/test/cross-user-access.test.ts b/packages/client/test/cross-user-access.test.ts index 3f10327..70f3832 100644 --- a/packages/client/test/cross-user-access.test.ts +++ b/packages/client/test/cross-user-access.test.ts @@ -24,8 +24,8 @@ import { openDocumentInbox, resetRegistryCache, walletInbox, -} from "../src/store-registry"; -import type { RegistrySession } from "../src/store-registry"; +} from "../src/shared-wallet/account-registry"; +import type { RegistrySession } from "../src/shared-wallet/account-registry"; import { configure, configureStoreRegistry, @@ -38,10 +38,10 @@ import { shareCap, connectedUser, } from "../src/polyfill"; -import { post, postToDocument, read as readInbox } from "../src/inbox"; -import { readUnion } from "../src/read-model"; -import { sparqlUpdate } from "../src/docs"; -import type { Nuri } from "../src/types"; +import { post, postToDocument, read as readInbox } from "../src/surface/inbox"; +import { readUnion } from "../src/surface/read-model"; +import { sparqlUpdate } from "../src/surface/docs"; +import type { Nuri } from "../src/model/types"; afterAll(() => { resetConfig(); diff --git a/packages/client/test/docs.test.ts b/packages/client/test/docs.test.ts index 8db278a..252394c 100644 --- a/packages/client/test/docs.test.ts +++ b/packages/client/test/docs.test.ts @@ -1,5 +1,5 @@ import { test, expect, mock, beforeEach } from "bun:test"; -import { docCreate, sparqlUpdate, sparqlQuery } from "../src/docs"; +import { docCreate, sparqlUpdate, sparqlQuery } from "../src/surface/docs"; // The reach guard is process-wide: once ANY cap exists it applies to every reader. // This suite declares none, so it must not inherit another suite's enforcement. @@ -7,7 +7,7 @@ beforeEach(() => { resetCaps(); setCurrentUser(null); }); -import * as ngProxy from "../src/ng-proxy"; +import * as ngProxy from "../src/surface/ng-proxy"; // NOTE ORDER: the "not configured → throw" case MUST run before any configure() // call, because configure() sets a module-level singleton with no public reset. diff --git a/packages/client/test/inbox.test.ts b/packages/client/test/inbox.test.ts index 24bcc54..0c079ef 100644 --- a/packages/client/test/inbox.test.ts +++ b/packages/client/test/inbox.test.ts @@ -1,7 +1,7 @@ import { test, expect, mock, beforeEach, afterAll } from "bun:test"; -import { post, read, materialize, watch } from "../src/inbox"; -import { walletInbox, resetRegistryCache } from "../src/store-registry"; -import type { Deposit } from "../src/inbox"; +import { post, read, materialize, watch } from "../src/surface/inbox"; +import { walletInbox, resetRegistryCache } from "../src/shared-wallet/account-registry"; +import type { Deposit } from "../src/surface/inbox"; import { configure, configureStoreRegistry, @@ -9,7 +9,7 @@ import { resetConfig, setCurrentUser, } from "../src/polyfill"; -import type { RegistrySession } from "../src/store-registry"; +import type { RegistrySession } from "../src/shared-wallet/account-registry"; // This suite injects a fake `ng` via configure() and reuses the storeRegistry's // injected session provider (inbox docs live in the shared wallet). Restore the diff --git a/packages/client/test/isolation-active.test.ts b/packages/client/test/isolation-active.test.ts index 809bc48..4c2a3d8 100644 --- a/packages/client/test/isolation-active.test.ts +++ b/packages/client/test/isolation-active.test.ts @@ -16,9 +16,9 @@ * (c) switching identity SWITCHES heldByHolder — it never wipes one. */ import { test, expect, mock, afterAll } from "bun:test"; -import { createEntityDoc, resetRegistryCache, walletInbox, listMyEntityDocs } from "../src/store-registry"; -import type { RegistrySession } from "../src/store-registry"; -import type { ReadCap } from "../src/types"; +import { createEntityDoc, resetRegistryCache, walletInbox, listMyEntityDocs } from "../src/shared-wallet/account-registry"; +import type { RegistrySession } from "../src/shared-wallet/account-registry"; +import type { ReadCap } from "../src/model/types"; import { configure, configureStoreRegistry, @@ -30,8 +30,8 @@ import { setCurrentUser, shareCap, } from "../src/polyfill"; -import { read as readInbox } from "../src/inbox"; -import { filterReadable } from "../src/read-filter"; +import { read as readInbox } from "../src/surface/inbox"; +import { filterReadable } from "../src/emulated-verifier/read-filter"; afterAll(() => { resetConfig(); @@ -356,7 +356,7 @@ test("a fresh session rebuilds the held caps from the scope index (the emulated // Listing my own documents refiles their caps: this is the store branch that // carries `AddRepo { read_cap }` upstream. - const { listMyEntityDocs } = await import("../src/store-registry"); + const { listMyEntityDocs } = await import("../src/shared-wallet/account-registry"); expect(await listMyEntityDocs("alice", "protected")).toEqual([doc]); expect(view(items)).toEqual(["p1"]); }); diff --git a/packages/client/test/machinery.test.ts b/packages/client/test/machinery.test.ts index b92f77d..d550def 100644 --- a/packages/client/test/machinery.test.ts +++ b/packages/client/test/machinery.test.ts @@ -7,7 +7,7 @@ * wrong in the other and real data silently disappears from reads. */ import { test, expect } from "bun:test"; -import { MACHINERY_NS, isMachinerySubject } from "../src/machinery"; +import { MACHINERY_NS, isMachinerySubject } from "../src/emulated-verifier/machinery"; test("the emulated branch subjects are all machinery", () => { // The four compartments store-registry emulates, verbatim. diff --git a/packages/client/test/ng-proxy.test.ts b/packages/client/test/ng-proxy.test.ts index 5880a22..79559fa 100644 --- a/packages/client/test/ng-proxy.test.ts +++ b/packages/client/test/ng-proxy.test.ts @@ -1,5 +1,5 @@ import { test, expect, mock, afterEach } from "bun:test"; -import { makeNg } from "../src/ng-proxy"; +import { makeNg } from "../src/surface/ng-proxy"; import { configure, resetConfig, diff --git a/packages/client/test/open-repo.test.ts b/packages/client/test/open-repo.test.ts index 2299fcc..7b02117 100644 --- a/packages/client/test/open-repo.test.ts +++ b/packages/client/test/open-repo.test.ts @@ -18,8 +18,8 @@ */ import { describe, it, expect, mock, beforeEach, afterAll } from "bun:test"; -import { ensureRepoOpen, ensureReposOpen, resetOpenedRepos } from "../src/open-repo"; -import { readUnion } from "../src/read-model"; +import { ensureRepoOpen, ensureReposOpen, resetOpenedRepos } from "../src/emulated-verifier/open-repo"; +import { readUnion } from "../src/surface/read-model"; import { configure, configureStoreRegistry, @@ -28,8 +28,8 @@ import { resetCaps, setCurrentUser, } from "../src/polyfill"; -import { resetInfrastructure } from "../src/reach"; -import { resetRegistryCache } from "../src/store-registry"; +import { resetInfrastructure } from "../src/emulated-verifier/reach"; +import { resetRegistryCache } from "../src/shared-wallet/account-registry"; afterAll(() => { resetConfig(); diff --git a/packages/client/test/reach.test.ts b/packages/client/test/reach.test.ts index 03b610a..5e52933 100644 --- a/packages/client/test/reach.test.ts +++ b/packages/client/test/reach.test.ts @@ -11,9 +11,9 @@ * how a link travels between users at all, and it gives the depositor nothing back. */ import { test, expect, mock, afterAll } from "bun:test"; -import { sparqlQuery, sparqlUpdate, depositInto } from "../src/docs"; -import { createEntityDoc, resetRegistryCache, walletInbox } from "../src/store-registry"; -import type { RegistrySession } from "../src/store-registry"; +import { sparqlQuery, sparqlUpdate, depositInto } from "../src/surface/docs"; +import { createEntityDoc, resetRegistryCache, walletInbox } from "../src/shared-wallet/account-registry"; +import type { RegistrySession } from "../src/shared-wallet/account-registry"; import { configure, configureStoreRegistry, @@ -22,8 +22,8 @@ import { resetCaps, setCurrentUser, } from "../src/polyfill"; -import { mayReach, mustNotAttempt } from "../src/reach"; -import { hasReadCap } from "../src/nuri"; +import { mayReach, mustNotAttempt } from "../src/emulated-verifier/reach"; +import { hasReadCap } from "../src/model/nuri"; afterAll(() => { resetConfig(); diff --git a/packages/client/test/read-filter.test.ts b/packages/client/test/read-filter.test.ts index 6636fe5..9508f59 100644 --- a/packages/client/test/read-filter.test.ts +++ b/packages/client/test/read-filter.test.ts @@ -1,6 +1,6 @@ import { test, expect } from "bun:test"; -import { filterReadable, makeReadFilteredView } from "../src/read-filter"; -import { CapRegistry } from "../src/caps"; +import { filterReadable, makeReadFilteredView } from "../src/emulated-verifier/read-filter"; +import { CapRegistry } from "../src/emulated-verifier/caps"; // The access unit is the DOCUMENT (an item's `@graph` = the repo it lives in), // not the item. Items here carry `@graph`; each holder holds caps per document. diff --git a/packages/client/test/read-model.test.ts b/packages/client/test/read-model.test.ts index 968ef88..8dd64d7 100644 --- a/packages/client/test/read-model.test.ts +++ b/packages/client/test/read-model.test.ts @@ -1,6 +1,6 @@ import { test, expect, mock, afterAll } from "bun:test"; -import { readUnion } from "../src/read-model"; -import type { Nuri } from "../src/types"; +import { readUnion } from "../src/surface/read-model"; +import type { Nuri } from "../src/model/types"; import { configure, configureStoreRegistry, diff --git a/packages/client/test/sparql.test.ts b/packages/client/test/sparql.test.ts index fa72aab..077f556 100644 --- a/packages/client/test/sparql.test.ts +++ b/packages/client/test/sparql.test.ts @@ -1,5 +1,5 @@ import { test, expect } from "bun:test"; -import { escapeLiteral, escapeIri, assertNuri } from "../src/sparql"; +import { escapeLiteral, escapeIri, assertNuri } from "../src/surface/sparql"; // --- escapeLiteral -------------------------------------------------------- diff --git a/packages/client/test/store-registry.test.ts b/packages/client/test/store-registry.test.ts index 03c98e8..eeeee60 100644 --- a/packages/client/test/store-registry.test.ts +++ b/packages/client/test/store-registry.test.ts @@ -8,8 +8,8 @@ import { walletInbox, createEntityDoc, resetRegistryCache, -} from "../src/store-registry"; -import type { RegistrySession } from "../src/store-registry"; +} from "../src/shared-wallet/account-registry"; +import type { RegistrySession } from "../src/shared-wallet/account-registry"; import { configure, configureStoreRegistry, diff --git a/packages/client/test/subscribe.test.ts b/packages/client/test/subscribe.test.ts index 72d2ed2..2fd617d 100644 --- a/packages/client/test/subscribe.test.ts +++ b/packages/client/test/subscribe.test.ts @@ -1,12 +1,12 @@ import { test, expect, mock, afterAll } from "bun:test"; -import { subscribeDoc, subscribeDocs } from "../src/subscribe"; +import { subscribeDoc, subscribeDocs } from "../src/surface/subscribe"; import { configure, configureStoreRegistry, resetConfig, resetStoreRegistry, } from "../src/polyfill"; -import type { RegistrySession } from "../src/store-registry"; +import type { RegistrySession } from "../src/shared-wallet/account-registry"; // subscribeDoc/subscribeDocs wrap the REAL injected `ng.doc_subscribe`. This // suite injects a fake `ng` whose `doc_subscribe` records the callback per doc diff --git a/packages/client/test/watch-shape.test.ts b/packages/client/test/watch-shape.test.ts index 2c75cf5..169ef7e 100644 --- a/packages/client/test/watch-shape.test.ts +++ b/packages/client/test/watch-shape.test.ts @@ -24,7 +24,7 @@ */ import { describe, it, expect, mock, beforeEach, afterEach, afterAll } from "bun:test"; -import { watchShape } from "../src/watch-shape"; +import { watchShape } from "../src/surface/watch-shape"; import { configure, configureStoreRegistry, @@ -33,8 +33,8 @@ import { resetCaps, setCurrentUser, } from "../src/polyfill"; -import { resetRegistryCache, createEntityDoc } from "../src/store-registry"; -import { resetOpenedRepos, setOpenTimeoutForTests, getSyncState } from "../src/open-repo"; +import { resetRegistryCache, createEntityDoc } from "../src/shared-wallet/account-registry"; +import { resetOpenedRepos, setOpenTimeoutForTests, getSyncState } from "../src/emulated-verifier/open-repo"; const TYPE = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type"; const FP = "http://festipod.org/";