feat(acl): owner may reclaim an orphaned/erased instance#174
Merged
Conversation
`may_write_instance` under `ESCUREL_WRITE_ACL=enforce` only let a caller overwrite an existing page if they resolved as its owner. When an instance's owner resolved to `None` — a `/delete-my-data` tombstone whose owner field was blanked, or an owner wikilink repointed at a deleted placeholder — the page became ownerless, so only admin could overwrite it. That permanently locked the rightful owner out of RE-CREATING their own instance (a member who erased their data could never re-onboard). Add a reclaim path on the OVERWRITE case, ONLY for owner-scoped skills (`owner_field.is_some()`): if the existing owner resolves to `None` (orphaned) AND the incoming content's owner resolves to the caller, allow the write. A live instance owned by someone else stays protected (existing owner `Some(other)` still denies), and public / no-`owner_field` instances stay admin-write-only (the `owner_field` guard keeps existing-None + incoming-None denied). CREATE path and admin bypass unchanged; reads and chat ACLs untouched. Tests (no mocks — real DuckDB + FsStore): owner reclaims their own orphaned wikilink instance and their own field-dropped direct-owner instance; a different non-admin still cannot overwrite a live instance owned by another; a public instance stays admin-write-only. Co-Authored-By: Claude Fable 5 <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.
The gap
Indexer::may_write_instance(the per-instance write ACL, enforced whenESCUREL_WRITE_ACL=enforce) only allowed a non-admin caller to OVERWRITE anexisting page if they resolved as that page's owner.
When an instance's owner resolves to None — e.g. a
/delete-my-datatombstone whose owner field was blanked, or an owner wikilink repointed at a
deleted placeholder — the page becomes ownerless. Under enforce, that left
the page admin-write-only, permanently locking the rightful owner out of
RE-CREATING their own instance. In the Carl agent this means a member who
erased their data could never re-onboard.
The rule (security-sensitive — precise)
On the OVERWRITE case (an existing page exists), only when the skill declares
an
owner_field(owner-scoped, not a public skill):incoming content's owner resolves to caller → allow (reclaim of an
orphaned instance: re-creating over your own erased tombstone).
protected).
The reclaim is guarded by
owner_field.is_some(), so public /no-
owner_fieldinstances stay admin-write-only exactly as before (existingowner None + incoming owner None still denies — they are never reclaimable).
The CREATE path (no existing page) and the admin bypass are unchanged; read
ACLs and chat ACLs are untouched.
Tests (no mocks — real DuckDB + FsStore, the project standard)
Added to
crates/escurel-index/tests/write_acl.rs, mirroring the existingowner_may_tombstone_own_instanceharness:owner_reclaims_own_orphaned_instance— alice tombstones her wikilink-ownedevent_profile(owner → deleted placeholder → None), then RE-CREATES itowned by herself → allowed. A different subject still cannot.
owner_reclaims_own_blanked_direct_owner_instance— same lifecycle for adirect-
owner_fieldskill (community_member, owner dropped) → allowed.live_instance_owned_by_other_stays_protected— bob cannot overwrite a liveinstance owned by alice, even claiming it for himself → denied.
public_orphaned_instance_stays_admin_write_only— a public / no-owner_fieldinstance is never reclaimable → denied for non-admin.
All existing acl / write-acl tests stay green.
cargo fmt,cargo clippy --all-targets -- -D warnings,cargo test -p escurel-index(incl.write_acl, 11 tests) and the server-sidewrite_acl/instance_acl/chat_acl/stored_query_aclintegrationtests all pass.
🤖 Generated with Claude Code