feat(guardrails): steering channel + shell-in-file rule + capability-gap detector
Bundles three operator-reliability guardrails (Vikunja #28/#29/#30) plus the in-flight branch WIP they were built on top of (reasoning_content capture, operator/project profile editor, write-jail workspaceRoot fix) — the tree is interdependent (SessionOrchestrator references reasoningArtifactId from the WIP) and does not compile as separable subsets, so it lands as one commit. Guardrails: - #28 mid-stage steering: ClientMessage.SteerSession -> GlobalStreamHandler -> orchestrator.submitSteering, reusing SteeringNoteAddedEvent + existing context fold (advisory, non-authoritative; invariants #3/#7). Closes the gap where steering typed off an approval gate was silently dropped. - #29 shell-in-file guardrail: ShellInFileContentRule (core:toolintent) blocks a file_write whose content is a bare shell command (e.g. "mkdir -p ..."); FileWriteTool description now advertises auto-mkdir of parent dirs. Basename-allowlist so the extensionless case is caught; scripts/Makefiles/multiline exempt. - #30 pt1 capability-gap detector: deterministic CapabilityGapDetector maps stage intent -> implied ToolCapability, compares to granted tools, emits advisory CapabilityGapDetectedEvent in FreestyleDriver.lockAndRun. Recorded, never fails the gate and never auto-grants (invariants #3/#4/#5). Reflection rung is pt2. Verified: ./gradlew check green (whole tree).
This commit is contained in:
@@ -151,6 +151,7 @@ func (m *Model) applyServer(msg protocol.ServerMessage) {
|
||||
if s := m.session(msg.SessionID); s != nil {
|
||||
s.CurrentStage = msg.StageID
|
||||
s.Tools = nil
|
||||
s.StageTokensUsed = 0
|
||||
s.addEvent(msg.OccurredAt, "StageStarted", msg.StageID)
|
||||
}
|
||||
case protocol.TypeStageCompleted:
|
||||
@@ -175,6 +176,9 @@ func (m *Model) applyServer(msg protocol.ServerMessage) {
|
||||
if msg.Response != "" {
|
||||
s.LastResponse = msg.Response
|
||||
}
|
||||
if msg.TotalTokens != nil {
|
||||
s.StageTokensUsed = *msg.TotalTokens
|
||||
}
|
||||
s.addEvent(msg.OccurredAt, "InferenceCompleted", msg.StageID)
|
||||
}
|
||||
case protocol.TypeInferenceTimeout:
|
||||
@@ -292,15 +296,36 @@ func (m *Model) applyServer(msg protocol.ServerMessage) {
|
||||
case protocol.TypeStageManifest:
|
||||
if s := m.session(msg.SessionID); s != nil {
|
||||
byStage := map[string][]ManifestTool{}
|
||||
budgetByStage := map[string]int{}
|
||||
for _, st := range msg.Stages {
|
||||
tools := make([]ManifestTool, 0, len(st.Tools))
|
||||
for _, td := range st.Tools {
|
||||
tools = append(tools, ManifestTool{Name: td.Name, Tier: td.Tier})
|
||||
}
|
||||
byStage[st.StageID] = tools
|
||||
if st.TokenBudget != nil {
|
||||
budgetByStage[st.StageID] = *st.TokenBudget
|
||||
}
|
||||
}
|
||||
s.ToolsByStage = byStage
|
||||
s.TokenBudgetByStage = budgetByStage
|
||||
}
|
||||
case protocol.TypeProjectProfile:
|
||||
errMsg := ""
|
||||
if msg.ConfigError != nil {
|
||||
errMsg = *msg.ConfigError
|
||||
}
|
||||
m.applyProjectProfileSnapshot(msg.About, msg.Conventions, msg.Commands, errMsg)
|
||||
case protocol.TypeOperatorProfile:
|
||||
errMsg := ""
|
||||
if msg.ConfigError != nil {
|
||||
errMsg = *msg.ConfigError
|
||||
}
|
||||
proposed := ""
|
||||
if msg.ProposedAdaptation != nil {
|
||||
proposed = *msg.ProposedAdaptation
|
||||
}
|
||||
m.applyOperatorProfileSnapshot(msg.About, msg.ApprovalMode, msg.PreferredModels, msg.Conventions, proposed, errMsg)
|
||||
case protocol.TypeProviderStatus:
|
||||
m.currentModel = msg.ProviderID
|
||||
if containsFold(msg.ProviderID, "llama") || containsFold(msg.ProviderID, "local") {
|
||||
@@ -411,7 +436,7 @@ func sessionIDOf(msg protocol.ServerMessage) string {
|
||||
protocol.TypeModelChanged, protocol.TypeModelList, protocol.TypeResourceStatus,
|
||||
protocol.TypeArtifactList, protocol.TypeConfigSnapshot, protocol.TypeSessionStats,
|
||||
protocol.TypeIdeaList, protocol.TypeHealthChecks,
|
||||
protocol.TypeFileList, protocol.TypeGrantList:
|
||||
protocol.TypeFileList, protocol.TypeGrantList, protocol.TypeOperatorProfile:
|
||||
return ""
|
||||
default:
|
||||
return msg.SessionID
|
||||
|
||||
Reference in New Issue
Block a user