diff --git a/docs/api-contract.md b/docs/api-contract.md index 7c099a4..47b1a5f 100644 --- a/docs/api-contract.md +++ b/docs/api-contract.md @@ -390,6 +390,8 @@ Consequences per function: --- +> **Known divergence, low impact today — `inbox.share` always deposits on the recipient's PROTECTED inbox.** Upstream the choice follows the profile through which the person was reached (`a_or_b = if details.profile.is_public() { "site" } else { "protected" }`, `engine/verifier/src/inbox_processor.rs:787`). This library has no notion of "which profile I know this person by", so it picks one. It flattens a distinction the model makes; it will be wrong the day an application shares with someone met through a public profile. Recorded rather than fixed, because the fix needs a notion nothing here has established — note that the `Identity` enum that would name it is entirely commented out upstream (`engine/repo/src/types.rs:586-595`), so there is no profile model to read yet. + ## 10. Capabilities — possession, not ACL ### Today diff --git a/packages/client/src/surface/inbox.ts b/packages/client/src/surface/inbox.ts index d908f6c..59b5cf8 100644 --- a/packages/client/src/surface/inbox.ts +++ b/packages/client/src/surface/inbox.ts @@ -296,7 +296,29 @@ export async function share(doc: NuriLike, toUser: string): Promise { `for it. A cap is looked up in what you hold, or it was delivered to you: ${JSON.stringify(target)}`, ); } - // Protected, because directed sharing is not a public announcement. + // ── KNOWN DIVERGENCE: the protected inbox is hard-coded here ────────────── + // Upstream the choice is not fixed. A contact record picks its inbox from the PROFILE + // through which the person was reached: `a_or_b = if details.profile.is_public() + // { "site" } else { "protected" }` (`engine/verifier/src/inbox_processor.rs:787`, + // written as `ng:site_inbox` vs `ng:protected_inbox` at `:823-824`). Reach someone by + // their public profile and the deposit goes to their public store's inbox; by their + // protected profile, to the protected one. + // + // This library has no notion of "the profile by which I know this person", so it + // always uses the protected one. Minor today — a consumer names a user and gets one + // answer — but it flattens a distinction the model makes, and the day an application + // shares with someone met through a public profile, this picks the wrong inbox. + // + // Not fixable in isolation: it needs a notion this library does not have, and about + // which nothing has been established here. What IS verified: a wallet holds `sites`, + // a `SiteV0` has an `id: PubKey`, a `name`, a `site_type` (Individual | Org) and three + // stores (`engine/verifier/src/site.rs:23-40`); the `Identity` enum that would name + // the rest is entirely COMMENTED OUT upstream (`engine/repo/src/types.rs:586-595`). + // Do not build on an assumed profile model — there is none to read yet. + // + // (The private store has no inbox at all — `new_store_default` attaches one only + // `if !private`, `verifier.rs:2994` — hence `InboxScope`, which makes "the private + // inbox" unwritable rather than merely empty.) await post(await userInbox(toUser, "protected"), { payload: { kind: LINK_KIND, cap } }); }