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:
2026-07-07 13:27:59 +04:00
parent 879672a47d
commit 41ed6414c6
43 changed files with 1592 additions and 94 deletions
+3 -6
View File
@@ -22,11 +22,8 @@ Emit your result as the `analysis` artifact (JSON, schema provided):
- `requirements`: concrete requirements / acceptance criteria, one per line.
- `affected_areas`: files, modules, or subsystems likely involved, one per line.
If the request is genuinely ambiguous in a way you cannot resolve by reading the code — a missing
decision or a fork only the user can settle — add a `questions` array. Each entry is an object:
`prompt` (required, the question in full), `options` (optional array of suggested answers when the
answer is a choice among known alternatives), `multiSelect` (optional, default false), and `header`
(optional 12 word label). Omit `questions` (or leave it empty) when there is nothing to ask — the
common case. The user answers in a form and their answers return to you for a re-run.
Always produce the analysis — it is your single exit. Resolving operator forks and open questions
is the **Discovery** stage's responsibility, upstream of you; treat the request as settled and do
not ask the user anything here. Anything you cannot answer, answer by reading the code.
Keep it factual and grounded in what you actually read. Do not propose a solution yet.