P14 — AI-workspace adoption in FxFiles
Surface the AI assistant's (MCP server's) workspace files in FxFiles' native category views, and additively adopt its tags — gated so non-AI users are unaffected.
Background
P13 lets a user pair an AI client (MCP). The AI writes encrypted objects to bucket fula-ai-workspace under keys ai/<category>/... (category ∈ images/videos/audio/documents) and a tag doc at ai/tag-metadata/ai-workspace.json, encrypted under a dedicated workspace secret = blake3DeriveKey('fula:ai-workspace-secret:v1', KEK). FxFiles re-derives the same secret and reads that forest with a separate client.
Per product decision #3, AI files appear in the native category views (carrying sourceBucket='fula-ai-workspace' so the UI can badge / route them).
Scope
- Workspace client: a second
EncryptedClient (_workspaceClient) in FulaApiService, built lazily + single-flight, gated on an existing AI connection, decrypted under the workspace secret. Its EncryptionConfig exactly mirrors the MCP's EncryptionConfig::from_secret_key (enableMetadataPrivacy: true, obfuscationMode: flatNamespace).
listWorkspaceObjects(bucket, {prefix}) + downloadWorkspaceObject(bucket, key) (the latter needed to read the AI tag doc, which the user's own client cannot decode).
- Category-view merge: the AI workspace is an additional live source for each category (prefix
ai/<category>/), merged into the same native view across all three merge seams (listCategoryMerged, listCategoryCached (mobile), listCategoryMergedCached (web)). AI items are seeded first so the user's own files always win a key collision; only ai/<base>/...-shaped keys are admitted.
- Tag adoption (additive):
restoreFromCloud reads the AI tag doc and additive-merges by id — a colliding (already-local) id is discarded entirely (untrusted-writer model); never clobbers a user's own tag.
Safety invariants
- No-op for non-AI users — every workspace read is gated behind
hasAiConnection().
- Never clobber the user's own files or tags (user-wins ordering; discard-colliding-tag-row).
- Error-isolated — any AI-side failure is tolerated as empty / swallowed, so it can never hide or block the user's own content.
Tests
Unit tests over the FulaApi fake + the category_listing pattern: images/documents views include their respective ai/<cat>/... items (sourceBucket carried); the gate (no connection ⇒ no workspace list call); the additive tag rule keeps a pre-existing local id and adds new ones.
Out of scope (follow-ups)
- Routing an AI item's download/thumbnail tap to the workspace client (the merge carries
sourceBucket; a follow-up wires the read path).
- A visible "AI" badge (carrying
sourceBucket is the requirement; badge is optional polish).
P14 — AI-workspace adoption in FxFiles
Surface the AI assistant's (MCP server's) workspace files in FxFiles' native category views, and additively adopt its tags — gated so non-AI users are unaffected.
Background
P13 lets a user pair an AI client (MCP). The AI writes encrypted objects to bucket
fula-ai-workspaceunder keysai/<category>/...(category ∈ images/videos/audio/documents) and a tag doc atai/tag-metadata/ai-workspace.json, encrypted under a dedicated workspace secret =blake3DeriveKey('fula:ai-workspace-secret:v1', KEK). FxFiles re-derives the same secret and reads that forest with a separate client.Per product decision #3, AI files appear in the native category views (carrying
sourceBucket='fula-ai-workspace'so the UI can badge / route them).Scope
EncryptedClient(_workspaceClient) inFulaApiService, built lazily + single-flight, gated on an existing AI connection, decrypted under the workspace secret. ItsEncryptionConfigexactly mirrors the MCP'sEncryptionConfig::from_secret_key(enableMetadataPrivacy: true,obfuscationMode: flatNamespace).listWorkspaceObjects(bucket, {prefix})+downloadWorkspaceObject(bucket, key)(the latter needed to read the AI tag doc, which the user's own client cannot decode).ai/<category>/), merged into the same native view across all three merge seams (listCategoryMerged,listCategoryCached(mobile),listCategoryMergedCached(web)). AI items are seeded first so the user's own files always win a key collision; onlyai/<base>/...-shaped keys are admitted.restoreFromCloudreads the AI tag doc and additive-merges by id — a colliding (already-local) id is discarded entirely (untrusted-writer model); never clobbers a user's own tag.Safety invariants
hasAiConnection().Tests
Unit tests over the
FulaApifake + thecategory_listingpattern: images/documents views include their respectiveai/<cat>/...items (sourceBucket carried); the gate (no connection ⇒ no workspace list call); the additive tag rule keeps a pre-existing local id and adds new ones.Out of scope (follow-ups)
sourceBucket; a follow-up wires the read path).sourceBucketis the requirement; badge is optional polish).