feat(workspace): Axis 2 Phase B — client→server workspace handshake

Wires the workspace handshake end to end so a session's workspace is bound
from the client's cwd at connect time and is event-sourced for replay.

- Go TUI sends Hello{workingDir=os.Getwd()} as the first WS frame on connect
  (protocol.go encoder + client.go connect path; golden test pins the wire
  format against the Kotlin discriminator).
- Server adds ClientMessage.Hello, stashes the per-connection workingDir, and
  on session start resolves it through WorkspaceResolver's trust pipeline,
  emits SessionWorkspaceBoundEvent (invariant #9), and threads the resolved
  workspace into OrchestrationConfig for the live run. A Hello after the first
  StartSession is ignored (warn); a rejected path binds the resolver fallback.
- Replay derives the workspace from the recorded event: SessionState gains
  boundWorkspace, DefaultSessionReducer fills it from SessionWorkspaceBoundEvent,
  and ReplayOrchestrator uses it (Path.of only, no filesystem re-query —
  invariant #8) with graceful fallback to config for pre-Phase-B logs.

Absent Hello / null resolver degrades to the prior config-workspace behavior.
This commit is contained in:
2026-06-03 13:18:17 +04:00
parent 57cf6f09f4
commit 622b331de3
15 changed files with 836 additions and 11 deletions
+8
View File
@@ -14,6 +14,14 @@ dependencies {
testImplementation "org.jetbrains.kotlin:kotlin-test"
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test"
testImplementation project(':infrastructure:persistence')
testImplementation project(':core:artifacts')
testImplementation project(':core:artifacts-store')
testImplementation project(':core:approvals')
testImplementation project(':core:context')
testImplementation project(':core:transitions')
testImplementation project(':core:validation')
testImplementation project(':core:risk')
testImplementation project(':core:tools')
}
tasks.named("koverVerify").configure { enabled = false }