fix(server,kernel,events): harden artifact read + restore workspace on reopen + cleanups

Follow-ups spotted while fixing the artifact-content bug:

- #1 robustness: listArtifacts resolves content via runCatching, degrading to null
  instead of failing the whole listing on one bad CAS read (resolveContent helper)
- #2 eviction visibility: WARN when a referenced artifact hash resolves to null
  (most likely evicted/compacted while still referenced by the event log)
- #3 ordering trap: document on ArtifactCreated/ArtifactContentStored that 'Created'
  fires at validation, AFTER ContentStored at inference — the latent fold hazard
- #4 reopen gap: SessionSnapshot now carries workspaceRoot (derived from
  SessionWorkspaceBound), so a reopened session restores its workspace label
- #5 orchestrator headroom: isBackEdge externalised to a top-level function so
  DefaultSessionOrchestrator drops below the TooManyFunctions threshold (was at it)

#6 (orphaned empty F-010-era artifacts) needs no code change — empty content is
already normalised to null ('(no content stored)'); emission is prevented going
forward by the shipped F-010/F-018/F-020 fixes.
This commit is contained in:
2026-06-10 12:58:28 +04:00
parent 5f7b6f1f18
commit cc6b402a23
6 changed files with 51 additions and 9 deletions
@@ -22,6 +22,13 @@ data class ArtifactValidatedEvent(
val stageId: StageId,
) : EventPayload
/**
* ⚠️ ORDERING TRAP: despite the name, this is emitted at the *validation* phase — AFTER
* [ArtifactContentStoredEvent] (which fires at inference time). Any fold that derives per-artifact
* state must NOT assume `ArtifactCreated` is the first event for an [artifactId]; seed accumulators
* from whichever artifact event arrives first (see StreamQueries.planArtifacts, which was bitten by
* exactly this — the content hash arrives one event earlier).
*/
@Serializable
@SerialName("ArtifactCreated")
data class ArtifactCreatedEvent(
@@ -31,6 +38,10 @@ data class ArtifactCreatedEvent(
val schemaVersion: Int,
) : EventPayload
/**
* Records the slot→CAS-hash mapping for a produced artifact. Emitted at inference time, so it
* precedes [ArtifactCreatedEvent] for the same [artifactId] (see the ordering note there).
*/
@Serializable
@SerialName("ArtifactContentStored")
data class ArtifactContentStoredEvent(