feat: dsh-gate — auto-run non-destructive mutations when no session ID
PolicyService.route now auto-runs config_mutation and reversible_low when sessionID is empty (dsh sidecar path), routing as 'dsh-gate'. Destructive still queues. This eliminates the two-layer consent issue: dsh pre-execute is the sole consent gate, oikos no longer queues dsh-originated mutations. Also updates the dsh plan with Phase 1-3 completion status and the architectural decision that _session_id injection is permanently deferred. v0.38.0
This commit is contained in:
@@ -169,6 +169,14 @@ func (s *PolicyService) route(ctx context.Context, in PolicySubmitInput, riskCla
|
||||
return PolicyDecision{Action: DecisionAuto, RiskClass: riskClass, Route: "auto-act"}
|
||||
}
|
||||
|
||||
// No session context (dsh sidecar or direct MCP call without nomos):
|
||||
// the agent runtime owns consent gating, so oikos auto-runs everything
|
||||
// except destructive without queueing. The destructive class still
|
||||
// queues because it requires explicit operator confirmation.
|
||||
if in.SessionID == "" && riskClass != policy.RiskDestructive {
|
||||
return PolicyDecision{Action: DecisionAuto, RiskClass: riskClass, Route: "auto-act", AutoViaWindow: "dsh-gate"}
|
||||
}
|
||||
|
||||
// Assent window: the operator approved the plan in this session;
|
||||
// config_mutation within the window auto-runs.
|
||||
if riskClass == policy.RiskConfigMutation && s.store.AssentWindowActive(ctx, in.AgentID, in.SessionID) {
|
||||
|
||||
@@ -144,6 +144,26 @@ func TestPlanFirstGate(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoSessionAutoRunExceptDestructive(t *testing.T) {
|
||||
svc, _ := newPolicySvc(t)
|
||||
noSession := func(cmd, risk string) PolicySubmitInput {
|
||||
return PolicySubmitInput{AgentID: agentID, TargetID: targetID, TargetSlug: "lxc:x", Command: cmd, DeclaredRisk: risk}
|
||||
}
|
||||
// config_mutation with no session → auto-run (dsh owns consent)
|
||||
d := svc.Decide(context.Background(), noSession("systemctl restart caddy", ""))
|
||||
if d.Action != DecisionAuto {
|
||||
t.Errorf("no-session config_mutation = %+v, want auto", d)
|
||||
}
|
||||
if d.AutoViaWindow != "dsh-gate" {
|
||||
t.Errorf("no-session config_mutation via = %q, want dsh-gate", d.AutoViaWindow)
|
||||
}
|
||||
// destructive with no session → still queue
|
||||
d = svc.Decide(context.Background(), noSession("rm -rf /data", ""))
|
||||
if d.Action != DecisionQueue {
|
||||
t.Errorf("no-session destructive = %+v, want queue", d)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSyntaxValidationGate(t *testing.T) {
|
||||
svc, _ := newPolicySvc(t)
|
||||
for _, bad := range []string{
|
||||
|
||||
Reference in New Issue
Block a user