From fec835b7d53d855b3a1d45c53ca9c8a3d86f20ea Mon Sep 17 00:00:00 2001 From: brainstorm-os Date: Mon, 3 Aug 2026 22:52:57 +0200 Subject: [PATCH] fix(collab): the dev bridge's provisionEntity now fans the DEK out to siblings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `SharingEngine.provisionEntity` minted a DEK via `dekStore.persist` and stopped. The production create path (`createEntityWithDek`) instead calls `installEntityWrap`, and the 10.3c sibling fan-out hangs off exactly that hook — so every entity the collab dogfood harness created left this device as the only DEK holder, and no dogfood session could reach the producer at all. This is why `collab/012-lan-two-devices` kept failing on `no DEK for entity ent_lan_brief` while every transport assertion passed: the LAN link was never the problem, the harness simply never asked for a wrap. With this, the producer is reached and behaves correctly — instrumenting the fan-out shows a rostered sibling with an X25519 key and a real ordinal: fanOut ent_lan_brief v1: roster=2 self=NXup0Zqb rows=KOB3NLjp:x,NXup0Zqb:x The spec still fails, on a separate and now precisely-located product bug: the joining device stays subscribed to its PRE-PAIRING identity inbox, so the wrap is delivered to a channel it no longer listens on. Filed as a follow-up rather than fixed here — that one is a sync/crypto change and wants its own rung and gates. Co-Authored-By: Claude Opus 5 (1M context) --- .../shell/src/main/collab/sharing-engine.ts | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/packages/shell/src/main/collab/sharing-engine.ts b/packages/shell/src/main/collab/sharing-engine.ts index c2636696..58a6257b 100644 --- a/packages/shell/src/main/collab/sharing-engine.ts +++ b/packages/shell/src/main/collab/sharing-engine.ts @@ -181,6 +181,14 @@ export class SharingEngine { const handle = dekStore.persist(entityId, dekId); dekStore.close(handle.dek); }); + // 10.3c — mirror the production create path. `createEntityWithDek` runs + // `installEntityWrap`, and the sibling fan-out hangs off exactly that + // hook, so a bridge that mints a DEK and stops leaves this device the + // only holder — and no dogfood session can ever reach the producer. + // That is precisely how `collab/012` kept failing on `no DEK for + // entity` with a working LAN link: the transport was never the problem, + // the harness simply never asked for a wrap. + await this.#fanOutProvisionedDek(entityId, type); } const exposed = this.#session.exposeIdentityForPairing(); await this.mutateAndEmit(entityId, (doc) => { @@ -194,6 +202,24 @@ export class SharingEngine { }); } + /** Fan a freshly-provisioned entity's DEK out to this identity's other + * devices. Never throws and never blocks the create: an offline sibling + * must not fail a local write, and the pairing backfill is the catch-up. */ + async #fanOutProvisionedDek(entityId: string, type: string): Promise { + try { + const dekStore = await this.ensureDekStore(); + const handle = dekStore.open(entityId); + if (!handle) return; + try { + await this.fanOutEntityWrapToSiblings(entityId, handle.dek, handle.version, type); + } finally { + dekStore.close(handle.dek); + } + } catch (error) { + console.warn(`[sharing] provision fan-out failed for ${entityId}: ${(error as Error).message}`); + } + } + /** * Owner-side share: verify the invite, append a signed grant + HPKE-wrap the * DEK into the doc (C2 `shareEntityWithInvite`), persist the doc delta, then