Skip to content

feat(engine): admit a write target below a proved grafted write root - #1910

Merged
FSM1 merged 1 commit into
mainfrom
feat/1886-admit-grafted-write-target
Sep 19, 2026
Merged

FSM1 merged 1 commit into
mainfrom
feat/1886-admit-grafted-write-target

Conversation

@FSM1

@FSM1 FSM1 commented Sep 19, 2026

Copy link
Copy Markdown
Owner

Closes #1886.

Part of #1880.

What this changes

The facade write guard now admits a write below a grafted root when this session has proved a write pass for that root. The proof is: the last tick built a grafted drain pass for the root, and the owner-committed permission is still write and the write scope seed is still in memory. The drain proves its facts again on each tick, and the adoption gate still judges every record.

This is the last slice of the writable-received-share build. The staging writable-share profile runs again.

The admitted and refused commands below a grafted root

Command Proved write pass Read grant, cut grant or unproved pass
Create, upload (new file), new version admitted ScopeExitRefused
Rename, move and relink inside the share admitted ScopeExitRefused
Delete admitted, unlink only ScopeExitRefused
Move or relink out of the share, into the share, or between two shares ScopeExitRefused ScopeExitRefused
Rename, delete or move of the grafted root itself ScopeExitRefused ScopeExitRefused
Any write at or below a scope root nested in the share (delete, move with replacing, create inside) ScopeExitRefused ScopeExitRefused
Restore into the share ScopeExitRefused ScopeExitRefused
Purge refused (the node is not in this vault's bin) refused
Version delete ScopeExitRefused ScopeExitRefused
Version restore ScopeExitRefused (see the follow-up below) ScopeExitRefused

A node that the render does not hold keeps the verdict that the rebase gives it.

The delete of a grantee (owner decision of 2026-09-19)

  • The facade journals the delete with to_bin = false below a grafted root. A grafted drain pass only unlinks the node: it writes no bin entry, re-keys nothing and journals no reclamation.
  • The owner engine bins the unlink by owner capture. Before this PR, owner capture did not see an unlink at the root of an interior scope, because the boundary walk merged the scope root without a report. install_descendant_scopes now reports those departures.

Defects found and fixed

  • A stranded op stalled the queue. An op journaled below a graft whose grant the sharer then cut never published, and it held the strict-FIFO head for ever. Read-only grafts are now keyless roots, so the pass that holds the identity charge parks the op as a dead letter and the queue drains.
  • Owner capture missed an unlink at a scope root (above).
  • Captures below a graft filled the bounded set. A focus leg below a grafted root now feeds no owner capture. No pass of this vault bins such a node; the sharer does.

Web

  • SnapshotView.permission reads write for a received share only when the write pass is proved. The file browser offers its write affordances on permission, and it hides share, version restore and version delete inside a received share.
  • tests/web-e2e/tests/write-grant.spec.ts (smoke slice, so the PR gate runs it): the recipient uploads, creates a folder and deletes inside the granted folder. The owner reads the writes back, and the deleted file appears in the owner bin and not in the recipient bin.
  • tests/web-e2e/staging/writable-share.spec.ts: the test.fixme() hold and its comment are removed.
  • The contact-code exchange moved into grantByCode in tests/web-e2e/sharing.ts. contact-grant.spec.ts uses it and now also asserts that a read grant offers no write.

Durable queue

No queued op gets a new field. The delete op already carries to_bin.

Tests

  • Facade (WriteGrantee harness, real facade and real drain): a_write_grantee_authors_every_admitted_write_inside_the_granted_scope, a_write_grantees_delete_only_unlinks_the_node, a_write_grantee_is_refused_what_leaves_the_scope_or_reaches_the_owners_surfaces, a_read_grant_or_an_unproved_write_pass_admits_no_write, an_op_a_cut_grant_strands_parks_rather_than_stalling_the_queue.
  • Unit: a_journal_target_outside_this_vaults_tree_is_refused (re-stated for write_home), a_grafted_relocation_stays_inside_one_share.
  • Two engines: a_write_grantees_delete_reaches_the_owners_bin_by_owner_capture in crates/engine/tests/owner_actions.rs. The owner grants write, the recipient engine accepts and deletes, and the owner engine bins the node.
  • a_version_command_on_a_file_outside_this_vaults_tree_is_refused (re-stated: it also refuses a new version where no write pass is proved).
  • Drain: a_grafted_pass_is_refused_every_vault_level_op_plan no longer lists the hard delete. Focus: a_withheld_child_is_neither_relinked_nor_removed now asserts that a grafted leg feeds no capture.
  • The stranded-op test, the nested-scope-root refusal and the two-engine owner-capture test each fail when their fix is removed.

Local runs: cargo fmt --all --check, clippy (workspace, and wasm32 for the engine and wasm crates), cargo test -p cipherbox-engine, pnpm -r typecheck, pnpm lint, pnpm lint:md, pnpm lint:tracker-refs, the apps/web vitest suite (761 tests). On the local web-e2e stack, write-grant.spec.ts, contact-grant.spec.ts, shared.spec.ts and the full-slice bin, sharing, invite, cross-client, write-path and save-path specs pass. The full local suite fails only on the login-button, device-approval and release-bundle specs, which need the Web3Auth build variables that this machine does not have.

Reviews run before the draft

  • Inline simplify, security and crypto-privacy passes.
  • An independent read-only reviewer pass over the diff. It found four items, and all four are fixed in this commit: the capture set above; a Move with replacing that could remove a nested scope root; a journal-time proof narrower than the drain's; and an own-scope exclusion that missed minted and unproved roots.
  • CodeRabbit CLI (--agent --base-commit): 0 findings.

Body checks / follow-ups filed

Note

Admit writes below a proved grafted write root in engine and web UI

  • Adds role-aware write-home classification so journal targets below a proved writable graft are authorized, while graft roots as nodes, read-only grafts, and unproved passes are refused. Relocations are admitted only within the same graft; cross-scope moves are refused.
  • SnapshotView now reports Permission::Write for a received scope only when the session holds a current proved write pass; otherwise it reports Read. The web FileBrowser enables its write surface on write permission, and FileBrowserActions exposes rename, move, and delete for write-granted received shares but omits sharing and version-history actions.
  • Grafted hard deletes unlink and republish without writing the owner's bin or doomed-manifest bookkeeping; soft/bin deletes in grafted scopes continue to be refused. Grafted focus passes no longer report departures for local capture.
  • install_descendant_scopes now accepts an observation timestamp and returns unlinked-child records so owner capture can process departures from adopted scope roots.
  • Behavioral Change: Engine::base_sequence_for and all mutation/upload arms now use role-aware write-home checks via Engine::write_home instead of the old blanket outside-vault refusal. A write below a received scope is refused when an intervening folder's published IPNS name is not derived from the grantee's cached write seed. Session reset clears the grafted_write_roots state. drain.rs publish_delete skips the vault-surface refusal when to_bin is false, allowing grafted hard deletes to proceed.

Macroscope summarized 0f2b413.

Summary by CodeRabbit

  • New Features

    • Write-enabled shared folders now support uploading files, creating folders, renaming, moving, viewing details, and deleting items.
    • Changes made in writable shares are visible to the folder owner.
  • Bug Fixes

    • Read-only shares consistently prevent write actions.
    • Deletions in writable shared folders now appear in the owner’s bin rather than the recipient’s bin.
    • Sharing, version restoration, and version deletion remain unavailable within received shared folders.

The facade write guard now admits a create, an upload, a new version, a
rename, a move and a delete below a grafted root when this session holds a
proved write pass for it. A read grant, an unproved pass and a node the
render does not hold keep their refusals.

A grantee delete only unlinks the node. It writes no bin entry and re-keys
nothing. The owner engine now captures an unlink at the root of an interior
scope too, so the node goes to the owner bin.

A move stays inside one received share. Restore, purge, version restore and
version delete below a grafted root stay refused. A write at or below a scope
root nested in the share is refused, because the grafted pass cannot seal
there and owner capture cannot bin a scope root. The facade admits a write
only below a root the last tick built a drain pass for.

An op that a cut grant strands below a graft is charged and parked as a dead
letter, so the queue behind it drains. A focus leg below a grafted root feeds
no owner capture, so the sharer's departures do not fill the bounded set.

The web client offers the in-share writes where the engine reports write
permission. The staging writable-share profile runs again, and a local
web-e2e spec covers the write grant in the PR gate.
@coderabbitai

coderabbitai Bot commented Sep 19, 2026

Copy link
Copy Markdown

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository: FSM1/cipher-box/.coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: b29ed195-e56e-49ab-bef6-1eae125bf392

📥 Commits

Reviewing files that changed from the base of the PR and between d1f05ee and 0f2b413.

📒 Files selected for processing (15)
  • apps/web/src/components/file-browser/FileBrowser.test.tsx
  • apps/web/src/components/file-browser/FileBrowser.tsx
  • apps/web/src/components/file-browser/FileBrowserActions.tsx
  • crates/engine/src/facade.rs
  • crates/engine/src/net/focus.rs
  • crates/engine/src/sync/drain.rs
  • crates/engine/tests/owner_actions.rs
  • crates/engine/tests/write_plane.rs
  • crates/wasm/src/lib.rs
  • packages/client/src/worker/protocol.ts
  • tests/web-e2e/README.md
  • tests/web-e2e/sharing.ts
  • tests/web-e2e/staging/writable-share.spec.ts
  • tests/web-e2e/tests/contact-grant.spec.ts
  • tests/web-e2e/tests/write-grant.spec.ts

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


Walkthrough

The engine now admits writes inside proved writable received shares, restricts operations to the granted scope, and routes deletions to owner capture. The web UI exposes permitted in-share actions while withholding owner-only actions. Unit, integration, and end-to-end tests cover these behaviors.

Changes

Writable received-share engine

Layer / File(s) Summary
Write-home routing and operation admission
crates/engine/src/facade.rs
Write targets now use role-aware WriteHome checks. Proved writable grafts admit in-scope create, upload, rename, move, relink, and version operations. Read-only, unproved, cross-scope, graft-root, bin, and version-history operations are refused.
Graft installation, draining, and deletion
crates/engine/src/facade.rs, crates/engine/src/net/focus.rs, crates/engine/src/sync/drain.rs
Grafted write roots are tracked during drain preparation. Grafted hard deletes publish unlinks without graft-side bin entries. Owner-plane capture receives departures.
Permission-based web affordances
apps/web/src/components/file-browser/*, crates/wasm/src/lib.rs, packages/client/src/worker/protocol.ts
The UI derives writability from snapshot permission. Writable received shares expose in-scope actions, but hide sharing and owner-only details actions. Documentation describes proved write permissions and scope limits.
Engine routing and capture validation
crates/engine/src/facade.rs, crates/engine/tests/owner_actions.rs, crates/engine/tests/write_plane.rs
Tests cover admitted operations, refused boundaries, dead letters, out-of-tree version refusal, and owner bin capture after a received-share deletion.
Contact-code and writable-share end-to-end coverage
tests/web-e2e/*
The shared grant helper now supports contact-code grants. E2E tests cover read-only behavior, writable uploads and folder creation, owner visibility, deletion propagation, and owner bin capture.

Priority: ➖ Normal

Estimated code review effort: 5 (Critical) | ~90 minutes

Change: Feature · Severity of issue fixed: Medium

Sequence Diagram(s)

sequenceDiagram
  participant RecipientUI
  participant RecipientEngine
  participant OwnerEngine
  participant OwnerCapture
  RecipientUI->>RecipientEngine: create or delete within writable share
  RecipientEngine->>RecipientEngine: validate proved write home
  RecipientEngine->>OwnerEngine: publish in-scope operation
  OwnerEngine->>OwnerCapture: report received deletion
  OwnerCapture->>OwnerEngine: create owner bin entry
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR satisfies the coding requirements in #1886. facade.rs adds role-aware WriteHome checks for proved grafted write roots, refuses read or unproved grafts, rejects graft roots and scope crossin…
Out of Scope Changes check ✅ Passed The changes stay within #1886. The web affordance changes, drain handling, owner-capture test, focus behavior, documentation updates, and end-to-end helpers support the writable received-share behavio…
Docstring Coverage ✅ Passed Docstring coverage is 88.24% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 17 functions across 12 files. (3 skipped: 1…
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the primary engine change: allowing writes below a proved grafted write root.
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@FSM1
FSM1 marked this pull request as ready for review September 19, 2026 10:02
@FSM1

FSM1 commented Sep 19, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@greptile-apps

greptile-apps Bot commented Sep 19, 2026

Copy link
Copy Markdown

RetriggerConfidence Score: 5/5

The PR appears safe to merge; no actionable correctness, security, or repository-rule violation remains.

Summary

This PR enables writes beneath a received share only after the engine proves a live grafted write pass, while preserving scope boundaries and owner-only operations.

  • Adds journal-time classification for owned, writable-grafted, nested-scope, and cross-share targets.
  • Adds grafted drain passes, stranded-operation handling, unlink-only grantee deletion, and owner-side unlink capture.
  • Exposes proved write permission through snapshot/WASM/client surfaces and enables corresponding web affordances.
  • Adds unit, integration, and browser coverage for accepted writes, refusals, revocation, deletion, and owner-bin behavior.
Diagram
sequenceDiagram
    participant UI as Web client
    participant F as Engine facade
    participant T as Resolve tick
    participant D as Grafted drain
    participant O as Owner engine

    T->>T: Re-prove grant, seeds, floors, and adoption gates
    T->>F: Record proved writable graft root
    UI->>F: Create, upload, rename, move, or delete
    F->>F: Classify write home and enforce scope boundaries
    alt proved in-share write
        F-->>UI: Journal accepted
        D->>D: Rebase and re-check records
        D->>D: Publish within grafted scope
        alt grantee delete
            D->>D: Unlink only
            O->>O: Observe departure and capture into owner bin
        end
    else read, cut, unproved, nested, or cross-scope target
        F-->>UI: ScopeExitRefused
    end
Loading

Reviews (1) · Last reviewed commit: "feat(engine): admit a write target below..."

@coderabbitai

coderabbitai Bot commented Sep 19, 2026

Copy link
Copy Markdown
⚠️ Action not completed

Already reviewed the last commit. Use @coderabbitai full review to rerun a review of the entire changeset.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@FSM1
FSM1 merged commit bc49321 into main Sep 19, 2026
45 checks passed
@FSM1
FSM1 deleted the feat/1886-admit-grafted-write-target branch September 19, 2026 10:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

engine: admit a write target below a proved grafted write root

1 participant