Repository navigation
VFS: accept a caller-supplied entry identity so a rebuilt tree keeps its own #102
Description
Summary
Let a caller supply an entry's identity when it creates one, so a namespace rebuilt from an external source of truth keeps the identities that source already issued.
Split from #89, which asked for identity in general and is answered by #96. What is left is the case that survives it: ino is stable through every operation that mutates a tree, and there is no way to preserve it through one that rebuilds a tree.
Why the general case is already closed
#96 put the identity on StatBase.ino, made it non-reusing, and cost nothing measurable. #98 made every content replacement — write, append, cp over a file — keep it. A consumer keying a per-file room, a grant, or an index row to a file is served by that for as long as the tree is mutated in place.
What a rebuild is, and why it is different
Restore a version, fork a project, re-materialise a workspace: the tree is reconstructed rather than mutated. Every entry is created fresh, so every identity is new, and everything keyed to the old ones is orphaned. A restore that produces a tree of new files holding the old content is not a restore.
Nothing in the library can be worked around here the way the earlier cases could. A host can keep its own identity map and rewrite it from vfs.mutation when paths move, because a move is a change it observes. A rebuild is not a change to observe — the host is the one performing it, and it has the identities in hand and no way to say them.
What changed since this was judged too expensive
#89 left this open on the grounds that a supplied identity would need a unique index to enforce, and that the index was the cost #96 avoided. That was true of the opaque-string design and is not true of the one that shipped:
id INTEGER PRIMARY KEYis an alias for the rowid.WHERE id = ?is a seek on the table's own key, and anINSERTwith an id that exists fails withSQLITE_CONSTRAINT_PRIMARYKEY. Verified against the schema:SEARCH vfs_entries USING INTEGER PRIMARY KEY (rowid=?), and a duplicate insert raises.- There is no index to add. The constraint is the table.
- The high-water mark already has the right shape.
updateUsagewritesMAX(next_ino, ?), so accepting a supplied identity is a matter of advancing the counter past it rather than new bookkeeping.
The honest limit
The primary key enforces uniqueness among live entries, not against retired identities. An identity whose entry was removed is free at the SQLite level, so a caller could supply one and the namespace would accept it. Nothing in the schema can tell reinstating the file that had it from recycling the number onto a different one — only the caller knows which it is doing.
That is an argument for saying so, not for refusing. VirtualFileSystem is already the trusted administration capability, and this is the same shape as setOwnership: the host asserts something the namespace cannot verify because the host is the only thing that can know it. A guard refusing anything below next_ino would be free and would make ordinary misuse impossible, but it forbids the case the feature exists for.
Scope
- An optional identity on the creating operations — at minimum
writeFile,mkdir,touch, andsymlink.copyand the opaque commit are worth deciding rather than assuming: a copy is a new file, so a supplied identity there may not mean anything. - Advance
next_inopast a supplied identity, so a later allocation cannot collide with one. - Refuse a supplied identity that a live entry holds, which the primary key does; decide the error code rather than surfacing a raw constraint failure.
- Decide whether a retired identity may be supplied. Recommended yes, documented as trusted, with the reasoning above.
- Decide whether the credential-bound view may supply one. Recommended no — it is administration, like opaque upload and the change feed.
Constraints
- Cost nothing when unused. Supplying an identity is a bound parameter on a statement that already runs; the allocation path must not gain work, and the metered counts must not move.
- Keep
copyset-based. It numbers its run from a base plusROW_NUMBER(), and whatever is decided for supplied identities there must not turn it into one statement per entry. - Do not weaken non-reuse. The counter must end up above anything ever handed out, supplied or allocated.
Acceptance criteria
- A tree rebuilt with supplied identities reports the same
inovalues as the tree it was rebuilt from. - A supplied identity held by a live entry is refused with a documented code.
- After supplying an identity above the counter, the next allocated one does not collide with it.
- Trusted-path statement, row-read, and row-written counts are unchanged when no identity is supplied.
docs/operations.mdstates what the namespace verifies and what it trusts the caller for.
Raised by the consumer in #89, who should weight accordingly.
Activity
akcorca commentedon Aug 23, 2026
Parked, with the reasoning, rather than closed
Deferred until a second consumer asks. The argument came from the consumer that asked for it in the first place, and it is better than the one in the issue body.
Two of the three motivating cases do not need it.
The third is avoidable, and the avoidance is better semantics. A restore is better done as a diff against the current tree, applying the writes, moves and removes that reconcile it. That preserves identities natively — every operation in that set already keeps them, per #96, #98 and #101 — and it does not churn revisions on files that did not change, which a full rebuild does.
And it is the only one of the identity issues that ships with a permanent caveat. The primary key enforces uniqueness among live entries only, so reinstating a retired identity is indistinguishable from recycling one onto a different file. That is explainable, and #96/#98/#101 need no such explanation — but it is carried forever, for every consumer, in exchange for a case the one consumer who wanted it can handle in its own code.
Two general things beats three. If in-place restore turns out not to work, this is where the reasoning is.
#105 and #107 are the live parts of the identity seam.