Workspace::open returns a workspace with an empty conversation index
- Status: Todo
- Kind: Chore
- Authors: jp
- Date: 2026-08-19
- Label: package=jp_workspace
- Label: type=bug
Workspace::open returns a value whose conversations() yields nothing, however many conversations the store holds, until the caller separately remembers load_conversation_index(). Nothing in the type says so, and the failure is silent: an empty iterator is indistinguishable from an empty workspace.
Every consumer today gets it right — jp_cli at lib.rs:500, jp_ffi in jp_workspace_open — so this is a hazard rather than a live bug. It is the kind that only surfaces at the third consumer.
Why open cannot simply load it
The obvious fix is wrong. sanitize.rs:15 requires sanitization to run first: "Call this before load_conversation_index to guarantee the backing store is consistent." jp_cli follows that order — the trashed-conversation sweep at lib.rs:480-493, then the index at line 500. Indexing inside open would index a store that has not been repaired.
Two shapes that work
A preparation operation that owns both steps. open stays as it is, and a single call runs sanitize-then-index in the required order, so a caller cannot get it half right. Cheapest, and leaves the current constructors alone.
Encode the unloaded state. open returns a value that has no conversation-reading methods, and load_conversation_index consumes it and returns one that does. Makes the mistake unrepresentable rather than merely documented, at the cost of a second type and a change to every construction site.
Both touch every construction site in jp_workspace and jp_cli, which is why neither rode along with the move of workspace opening into jp_workspace.
Interim
open's doc comment states the contract: the index is not populated, load_conversation_index is the caller's next step, and sanitize comes before it. That paragraph can go once the ordering is enforced rather than described.