feat(spaces): wire active-space boot into (app) layout

Calls loadActiveSpace() + reconcileSentinels() in the Phase-B critical
boot block, right after the user identity is bound to the ambient actor
and before sync starts. This means:

- Pending-change rows pushed to mana-sync carry the real organization
  id, not the `_personal:<userId>` sentinel the v28 migration uses
  as a placeholder.
- Sentinel records (written pre-boot or by the v28 upgrade on an
  existing db) get rewritten to the real personal-space id in a single
  pass once Better Auth responds.
- The scope wrapper in module queries now partitions by the active
  space instead of degrading to sentinel-only filtering.

Failure is non-fatal — an offline boot or a Better Auth hiccup just
means the sentinel path stays live and the next boot retries. A count
log surfaces the reconciliation count so migrations are visible in
devtools.

Plan: docs/plans/spaces-foundation.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-04-20 18:10:46 +02:00
parent 76060b0632
commit 871a1c3bba

View file

@ -558,6 +558,25 @@
const uid = authStore.user?.id ?? 'unknown';
const name = authStore.user?.name || authStore.user?.email || 'Du';
bindDefaultUser(uid, name);
// Spaces foundation: resolve the user's active Space from Better
// Auth before sync runs so pending-change rows get stamped with a
// real organization id (not the `_personal:<userId>` sentinel the
// v28 migration leaves behind). Failure is non-fatal — the scope
// wrapper falls back to the sentinel, sync still pushes with a
// NULL space_id, and the next boot retries. See
// docs/plans/spaces-foundation.md.
try {
const { loadActiveSpace, reconcileSentinels } = await import('$lib/data/scope');
await loadActiveSpace();
const rewritten = await reconcileSentinels();
if (rewritten > 0) {
console.info(`[spaces] reconciled ${rewritten} sentinel records to active space`);
}
} catch (err) {
console.warn('[spaces] active-space boot failed — sync will use sentinel scope', err);
}
await syncBilling.load();
const getToken = () => authStore.getValidToken();
unifiedSync = createUnifiedSync(SYNC_SERVER_URL, getToken, syncBilling.active);