feat(shared-ai): SYSTEM_BOOTSTRAP system source — fallback inserts now stamp origin='system'

The race-window `getOrCreateLocalDoc()` fallback in userContextStore +
kontextStore stays (without it, a write that lands between "endpoint
provisioned the singleton in mana_sync" and "first pull landed it in
IndexedDB" would hit `update(missing-id, diff)` — a Dexie no-op that
silently swallows the user's edit). But it was semantically lying: the
insert stamped `origin='user'` even though the row is logically a
client-side replica of the server-side bootstrap.

This commit adds `SYSTEM_BOOTSTRAP = 'system:bootstrap'` to
`@mana/shared-ai` and wraps the two fallback inserts in
`runAsAsync(makeSystemActor(SYSTEM_BOOTSTRAP), ...)`. The Dexie hook
now stamps `origin: 'system'` on the empty-row insert — structurally
identical to the row mana-auth's bootstrap-singletons.ts writes. When
the server's pull arrives later both sides carry the same origin and
the conflict-gate stays quiet. The user's subsequent writes still
stamp `origin: 'user'` on the changed fields.

Plan: docs/plans/sync-field-meta-overhaul.md (F4-fu Fallback-Origin row).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-04-27 01:44:30 +02:00
parent 01681b58ff
commit ae6a14fb76
6 changed files with 61 additions and 20 deletions

View file

@ -154,7 +154,8 @@ _Wird befüllt während der Ausführung._
| F7 | `2a8e8ff98` | `repair-silent-twin.ts` + `legacy-avatar.ts` Migrationen ersatzlos gelöscht. Beide existierten nur um die Symptome eines fixed-in-M2.5 Bugs zu cleanen, der pre-live keine echten Daten produziert hat. Mit F2's `origin='migration'` wrapper + F3's drop von synced `updatedAt` würden ihre writes auch nicht mehr als Conflicts auftauchen — sie waren strukturell überflüssig. Caller in MeImagesView + wardrobe/ListView entfernt; leere `migration/` Directory gelöscht. |
| F4-fu (kontextDoc) | `3df739190` | F4-Symmetrie: `bootstrapSpaceSingletons(spaceId, ownerUserId, syncSql)` in `bootstrap-singletons.ts`, schreibt einen leeren `kontext/kontextDoc` row pro Space-Erstellung in `mana_sync.sync_changes` mit `client_id='system:bootstrap'`, `origin='system'`. Zwei Aufruf-Sites: `databaseHooks.user.create.after` (Personal-Space; nur wenn `createPersonalSpaceFor` `created: true` zurückgibt) + `organizationHooks.afterCreateOrganization` (alle non-personal Spaces). `createBetterAuth` kriegt `syncDatabaseUrl` als zweites Argument; lazy module-scoped postgres-pool. Webapp `kontextStore.ensureDoc()` zu privat `getOrCreateLocalDoc()` umbenannt — Public-API ist nur noch `setContent` + `appendContent`. Kontext content bleibt encrypted at rest auf dem Client (`kontextDoc.content`); der Server-Bootstrap schreibt `''` plaintext, was im Client-`decryptRecord` toleriert wird (non-envelope strings werden durchgereicht). |
| F3-fu (v55 cleanup) | `53fecbf4a` | Dexie v55 löscht den Orphan-`updatedAt`-Wert aus jedem Row in `Object.keys(TABLE_TO_APP)`. v53 hatte ihn bewusst stehengelassen (Comment "next-version upgrade can drop it"); nach F3 liest niemand mehr `row.updatedAt``deriveUpdatedAt(local)` aus `__fieldMeta` ist die SSOT. Idempotent (delete missing field = no-op), best-effort (try/catch pro Tabelle für unbekannte/missing stores). |
| F4-robust (Endpoint) | _pending_ | F4-Bootstrap robuster gemacht via expliziten Endpoint `POST /api/v1/me/bootstrap-singletons` in mana-auth. Beide Bootstrap-Funktionen (`bootstrapUserSingletons`, `bootstrapSpaceSingletons`) sind jetzt idempotent (existence-check vor INSERT) und geben `boolean` zurück. Endpoint ruft beide auf — userContext für den Caller, kontextDoc für jeden Space, in dem der Caller Member ist. Webapp `(app)/+layout.svelte` callt den Endpoint einmal pro Boot vor `createUnifiedSync`, fire-and-forget am Client. Signup-Hooks (`databaseHooks.user.create.after`, `organizationHooks.afterCreateOrganization`) bleiben als happy-path; Endpoint ist Reconciliation belt-and-suspenders. |
| F4-robust (Endpoint) | `099cac4a0` | F4-Bootstrap robuster gemacht via expliziten Endpoint `POST /api/v1/me/bootstrap-singletons` in mana-auth. Beide Bootstrap-Funktionen (`bootstrapUserSingletons`, `bootstrapSpaceSingletons`) sind jetzt idempotent (existence-check vor INSERT) und geben `boolean` zurück. Endpoint ruft beide auf — userContext für den Caller, kontextDoc für jeden Space, in dem der Caller Member ist. Webapp `(app)/+layout.svelte` callt den Endpoint einmal pro Boot vor `createUnifiedSync`, fire-and-forget am Client. Signup-Hooks (`databaseHooks.user.create.after`, `organizationHooks.afterCreateOrganization`) bleiben als happy-path; Endpoint ist Reconciliation belt-and-suspenders. |
| F4-fu (Fallback-Origin) | _pending_ | Punkt 4 abgeschwächt: `getOrCreateLocalDoc()` in userContextStore + kontextStore bleibt (Race zwischen "Endpoint provisioniert in mana_sync" und "First-Pull landet in IndexedDB" lässt sich nicht eliminieren — ohne Fallback würden Writes im Race-Window silently in `update(missing-id, diff)` no-ops verloren gehen). Aber: Fallback-Insert ist jetzt in `runAsAsync(makeSystemActor(SYSTEM_BOOTSTRAP), ...)` gewrappt. Neue Konstante `SYSTEM_BOOTSTRAP = 'system:bootstrap'` in `@mana/shared-ai`, mappt via `originFromActor` auf `origin='system'` — strukturell äquivalent zum Server-Bootstrap. Wenn der Server-Pull später ankommt, beide Rows tragen `origin: 'system'`, conflict-gate bleibt ruhig. User-Writes danach stempeln `origin: 'user'` wie immer. |
## F1 — Implementation Notes