IndexBuild
A sidecar build running on the index catalog's own thread.
val build = indexes.createIndexInBackground(store, IndexDefinition.inverted("$.team"))
build.handle // usable immediately — see below
println(build.progress) // IndexBuildProgress(RUNNING, 12/40 segment(s), 12 built)
build.cancel() // stops at the next segment boundary
build.await() // blocks until it has, rethrowing any failureThe handle is usable the moment this returns, and that is not a convenience. createIndex already makes the definition durable before a single posting file exists, because the alternative leaves posting files for an index nothing knows about. A background build inherits that ordering unchanged, so by the time there is an IndexBuild there is a registered index — one that covers no segments yet, which every query already handles by scanning. There is no cutover and never was one.
Cancellation is safe because coverage is honest. cancel stops the pass at the next segment boundary and undoes nothing: what is left is an index defined over some segments and not others, which is indistinguishable from a build that is still running, from one a crash interrupted, and from one whose segment hit its term budget. IndexCoverage reports it, queries scan what is not covered, and a later createIndexInBackground for the same definition finishes the job. Resumption is not a feature here — it is what the per-segment sidecar design has always implied, and this is the first thing able to reach it.
The segment in flight is finished rather than abandoned. Abandoning writes nothing — nothing is written until the observation completes — so it would throw away a scan that is nearly done and leave exactly the segment a resumed build has to redo first. One segment is the granularity of the whole design, so it is the granularity of stopping too.
Thread safety. Safe from any thread. Every field is a snapshot taken under one lock.