Skip to content

feat(acl): owner may reclaim an orphaned/erased instance#174

Merged
jrosskopf merged 1 commit into
mainfrom
feat/acl-owner-reclaim-erased-instance
Jun 15, 2026
Merged

feat(acl): owner may reclaim an orphaned/erased instance#174
jrosskopf merged 1 commit into
mainfrom
feat/acl-owner-reclaim-erased-instance

Conversation

@jrosskopf

Copy link
Copy Markdown
Contributor

The gap

Indexer::may_write_instance (the per-instance write ACL, enforced when
ESCUREL_WRITE_ACL=enforce) only allowed a non-admin caller to OVERWRITE an
existing page if they resolved as that page's owner.

When an instance's owner resolves to None — e.g. a /delete-my-data
tombstone 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):

  • existing owner resolves to caller → allow (unchanged).
  • else if existing owner resolves to None (orphaned/erased) AND the
    incoming content's owner resolves to callerallow (reclaim of an
    orphaned instance: re-creating over your own erased tombstone).
  • else → deny (unchanged: a live instance owned by someone else stays
    protected).

The reclaim is guarded by owner_field.is_some(), so public /
no-owner_field instances stay admin-write-only
exactly as before (existing
owner 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 existing
owner_may_tombstone_own_instance harness:

  1. owner_reclaims_own_orphaned_instance — alice tombstones her wikilink-owned
    event_profile (owner → deleted placeholder → None), then RE-CREATES it
    owned by herself → allowed. A different subject still cannot.
  2. owner_reclaims_own_blanked_direct_owner_instance — same lifecycle for a
    direct-owner_field skill (community_member, owner dropped) → allowed.
  3. live_instance_owned_by_other_stays_protected — bob cannot overwrite a live
    instance owned by alice, even claiming it for himself → denied.
  4. public_orphaned_instance_stays_admin_write_only — a public / no-owner_field
    instance 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-side
write_acl / instance_acl / chat_acl / stored_query_acl integration
tests all pass.

🤖 Generated with Claude Code

`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>
@jrosskopf jrosskopf merged commit a447a46 into main Jun 15, 2026
1 check passed
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.

1 participant