feat(fula-js): expose putFlat, getFlat, listFilesFromForest (forest-tracked I/O)#82
Merged
Merged
Conversation
…racked I/O) The WASM bindings only exposed `putEncryptedWithType` — a RAW encrypted-object PUT that writes the blob but never registers it in the bucket's private-forest index. Objects written that way are readable only by their exact obfuscated key and are INVISIBLE to listDecrypted / listFilesFromForest / listDirectory. That is why the hosted MCP Worker's AI-workspace writes never appeared in FxFiles. Add three additive bindings wrapping EXISTING, unchanged fula-client methods that FxFiles' own Flutter FFI already uses for every file it writes/reads: - putFlat -> EncryptedClient::put_object_flat (upsert + flush_forest) - getFlat -> EncryptedClient::get_object_flat (forest + CID-aware read) - listFilesFromForest -> EncryptedClient::list_files_from_forest (forest enumeration) No fula-client logic changes; purely additive exports. Verified end-to-end against the prod gateway via a Node harness on the freshly-built wasm: putFlat -> listFilesFromForest enumerates the write -> getFlat round-trips byte-exact. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0161UGEJmTpM6DM2cVZyV6Ev
ehsan6sha
added a commit
to functionland/pinning-service
that referenced
this pull request
Jun 24, 2026
…stFilesFromForest) (#68) The hosted MCP Worker stored AI files with putEncryptedWithType — a raw encrypted- object PUT that never registers the object in the bucket's private-forest index. Result: AI files were readable by exact key but NOT enumerable, so they never appeared in FxFiles (or the AI's own fula_list_files). See functionland/fula-api#82. Switch every workspace I/O path to the forest-tracked bindings (the same ones the FxFiles app already uses): - storeFile + applyTaggingToDoc putEncryptedWithType -> putFlat (upsert + flush) - readFile + tag-doc read getDecrypted -> getFlat (forest + CID read) - listFiles + search listDecrypted -> listFilesFromForest (listFiles' category narrowing moves to the post-filter; the raw listDecrypted prefix-filtered the OBFUSCATED storage keys and returned nothing for this bucket.) Concurrency: put_object_flat flushes the forest with a conditional PUT; a concurrent writer makes it 412 (ConcurrentModification). The wasm flush is single-attempt, so withWorkspaceClient now retries on 412 — re-creating the client drops the stale forest cache so the next attempt reloads the winner's forest and re-applies the write (bounded + jittered; read-only bodies never 412). Requires @functionland/fula-client ^0.6.17 (the build that exports putFlat/getFlat/ listFilesFromForest). Verified: tsc clean + a workerd (vitest-pool-workers) integration test driving putFlat -> listFilesFromForest -> getFlat against the prod gateway, in the Worker runtime, round-trips byte-exact. Claude-Session: https://claude.ai/code/session_0161UGEJmTpM6DM2cVZyV6Ev Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
ehsan6sha
added a commit
to functionland/FxFiles
that referenced
this pull request
Jun 25, 2026
…rol (1.i/1.ii/1.iii) (#93) After the Worker writes AI files forest-indexed (functionland/pinning-service#68 + functionland/fula-api#82), FxFiles can see + move them. Three parts: P1 (1.i) category map: the MCP writes SINGULAR category segments (ai/document/, ai/note/, ai/image/ …); FxFiles' category views are PLURAL. _mergeAiWorkspaceInto now maps them (images<-image+screenshot, documents<-document+note+link, other<-file+other) and folds the AI categories with no FileCategory of their own into the closest view. A view with no AI mapping (downloads/archives/starred) short-circuits. P2 (1.i) cloud-files: the bucket view routes fula-ai-workspace through the workspace client (workspace secret) instead of the master-KEK listObjects, so the AI files decrypt + list, tagged sourceBucket for correct open/move routing. P3 (1.ii/1.iii) move-as-access-control: moving a file INTO the AI bucket re-encrypts it under the workspace secret + forest-indexes it (grant AI read) under an ai/<category>/<name> key; moving OUT re-encrypts under the master KEK and removes the workspace copy (revoke). The revoke is VERIFIED: the re-encrypted master copy must decrypt BEFORE the AI copy is deleted, then the AI copy is verified gone — "revoke incomplete" is surfaced loudly otherwise. All re-keying is client-side (the KEK never leaves FxFiles). New primitives: uploadWorkspaceObject (putFlat), deleteWorkspaceObject (deleteFlat, which removes the forest entry too). The move orchestration is factored into a UI-free aiAwareMove() so the security- critical revoke is unit-tested (grant / verified-revoke / abort-without-delete-on- failed-verify / normal-move). flutter analyze clean (0 errors); 25 AI unit tests green. Claude-Session: https://claude.ai/code/session_0161UGEJmTpM6DM2cVZyV6Ev Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
ehsan6sha
added a commit
that referenced
this pull request
Jun 25, 2026
#83) * test(gateway): update stale auth.rs MCP-scope test to match a12ed4d a12ed4d ("scope MCP tokens to dedicated bucket, not ai/ key prefix") deliberately stopped enforcing the `ai/` key prefix in McpScope::assert — AI-workspace keys are obfuscated (flat-namespace/metadata-privacy for FxFiles compat) so real S3 keys never carry `ai/`, and the prefix check was 403-ing every legitimate AI write. That commit updated the mcp_scope.rs test but missed a DUPLICATE assertion in auth.rs (mcp_token_aimage_boundary_key_is_access_denied), which kept asserting the removed behavior and failed CI — surfaced only when #82's fula-js change triggered Flutter CI (the test-rust job had not run on a12ed4d). Update the stale auth.rs test to match the intended design: within the scoped DEDICATED bucket the key prefix is NOT enforced (isolation is bucket + (hashed_user_id,bucket) + sub); a DIFFERENT bucket is still denied. No production code change. fula-cli --lib now 227/227 green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0161UGEJmTpM6DM2cVZyV6Ev * test(gateway): also assert an obfuscated storage key is allowed (codex review) --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The hosted MCP Worker stores AI files into
fula-ai-workspace, but they never appear in FxFiles. Root cause (proven empirically with a native probe and a real-wasm Node harness against the prod gateway): the Worker's only upload binding,putEncryptedWithType, is a RAW encrypted-object PUT — it writes the blob but never registers it in the bucket's private-forest index, so the file is readable only by its exact obfuscated key and invisible to every list path. FxFiles' own uploads don't have this problem because the Flutter FFI uses the forest-trackedput_object_flat.Change
Three additive
#[wasm_bindgen]exports infula-js, each wrapping an existing, unchangedfula-clientmethod (the same ones the Flutter FFI already uses):putFlatput_object_flatgetFlatget_object_flatlistFilesFromForestlist_files_from_forestNo
fula-clientlogic changes — purely new exports. Normal files (FxFiles via the FFI) are unaffected; the format is identical.Test
Node harness on the freshly-built wasm, against the prod gateway with a real workspace secret:
putFlata file →listFilesFromForestenumerates it →getFlatround-trips byte-exact. ✅{storageKey, originalKey, …}(camelCase), matching the existinglistDecryptedcontract.Follow-up
The hosted Worker (separate PR) switches
storeFile→putFlat,fula_read_file→getFlat,listFiles/search→listFilesFromForest. After merge,@functionland/fula-clientmust be republished with the new exports.🤖 Generated with Claude Code