Summary
For a single user, the object pins and the encrypted-forest pins (the bare manifest / manifest-page / HAMT-node blocks) are stored in the pins table under two different user_id values. Any user-scoped maintenance query keyed on the object owner's hash silently misses the forest pins.
Observed (production, one user, documents bucket)
- Object pins
object:documents/<key> -> pins.user_id = 1492a9c9... (the object owner hash)
- Forest pins (bare flat keys: manifest
Qmfd462..., manifest pages, HAMT nodes) -> pins.user_id = 2d2dfffd... (a different hash)
- Both are the same user's
documents data; the forest pins decrypt with the same forest_dek and fully reconstruct the bucket (17 files recovered).
How the object owner hash is derived
session.hashed_user_id = hash_user_id(user_id) (BLAKE3) — crates/fula-cli/src/handlers/admin.rs:1041, used as the S3 object owner (crates/fula-cli/src/handlers/bucket.rs:26; handlers/object.rs with_owner(&session.hashed_user_id)). The object pins' user_id matches this; the forest/bare pins carry a different value, i.e. they were stamped from a different underlying user_id.
Impact
- User-scoped queries miss the forest. The index-rebuild bare-key scan, the users-index publisher (
UserBucketsIndex/GlobalUsersIndex), and any GC/replication accounting filtered on WHERE user_id = <object hash> will not see the forest blocks. In a real recovery, a user_id-scoped bare export returned zero forest blocks (all under the second id), forcing a per-CID on-demand fallback.
- Risk of forest blocks being mis-attributed / orphaned in any per-user lifecycle operation.
Leading hypothesis
The forest predates an identity / userKey change: the underlying user_id hashing to 2d2dfffd... is older, while recent object writes hash to 1492a9c9... (possibly related to the client userKey_v2 derivation change). Likely legacy data.
Proposed follow-up (post-recovery; not urgent)
- Determine which input produces
2d2dfffd... vs 1492a9c9... (old vs new user_id, or a different stamping path for bare/forest pins).
- Decide whether to backfill/migrate forest pins to the canonical
hashed_user_id.
- Make user-scoped maintenance queries forest-aware, or unify the
user_id at write time so object and forest pins always share one owner.
Context
Surfaced during the post-ipfs repo gc recovery (index-node-loss / safe-gc work). Filed for documentation; to be addressed after recovery completes.
Summary
For a single user, the object pins and the encrypted-forest pins (the bare manifest / manifest-page / HAMT-node blocks) are stored in the
pinstable under two differentuser_idvalues. Any user-scoped maintenance query keyed on the object owner's hash silently misses the forest pins.Observed (production, one user,
documentsbucket)object:documents/<key>->pins.user_id = 1492a9c9...(the object owner hash)Qmfd462..., manifest pages, HAMT nodes) ->pins.user_id = 2d2dfffd...(a different hash)documentsdata; the forest pins decrypt with the sameforest_dekand fully reconstruct the bucket (17 files recovered).How the object owner hash is derived
session.hashed_user_id = hash_user_id(user_id)(BLAKE3) —crates/fula-cli/src/handlers/admin.rs:1041, used as the S3 object owner (crates/fula-cli/src/handlers/bucket.rs:26;handlers/object.rswith_owner(&session.hashed_user_id)). The object pins'user_idmatches this; the forest/bare pins carry a different value, i.e. they were stamped from a different underlyinguser_id.Impact
UserBucketsIndex/GlobalUsersIndex), and any GC/replication accounting filtered onWHERE user_id = <object hash>will not see the forest blocks. In a real recovery, auser_id-scoped bare export returned zero forest blocks (all under the second id), forcing a per-CID on-demand fallback.Leading hypothesis
The forest predates an identity / userKey change: the underlying
user_idhashing to2d2dfffd...is older, while recent object writes hash to1492a9c9...(possibly related to the clientuserKey_v2derivation change). Likely legacy data.Proposed follow-up (post-recovery; not urgent)
2d2dfffd...vs1492a9c9...(old vs newuser_id, or a different stamping path for bare/forest pins).hashed_user_id.user_idat write time so object and forest pins always share one owner.Context
Surfaced during the post-
ipfs repo gcrecovery (index-node-loss / safe-gc work). Filed for documentation; to be addressed after recovery completes.