mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-08-21 06:53:29 +00:00
107 lines
4.5 KiB
Go
107 lines
4.5 KiB
Go
package tools
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/rcourtman/pulse-go-rewrite/internal/agentcapabilities"
|
|
)
|
|
|
|
// ExecutionProfile is the core-owned, request-local execution posture for
|
|
// one Assistant/Patrol run. It is deliberately an opaque integer with no
|
|
// JSON tags, string round-trip, or public constructor from external input:
|
|
// enterprise and transport code can never serialize, select, or relax a
|
|
// profile. Profiles are applied by core call sites only (chat service and
|
|
// the investigation adapter), and their policy is enforced twice - during
|
|
// provider projection and again at registry execution.
|
|
type ExecutionProfile int
|
|
|
|
const (
|
|
// ProfileInteractiveAssistant is the default interactive chat
|
|
// posture: the operator's configured control level and autonomy
|
|
// govern, and the interactive question tool is available.
|
|
ProfileInteractiveAssistant ExecutionProfile = iota
|
|
// ProfilePatrolDetection is the scheduled Patrol detection posture:
|
|
// non-interactive, no infrastructure mutations, and Pulse-state
|
|
// mutations restricted to the finding lifecycle and observer-proposal tools
|
|
// (patrol_report_finding / patrol_assess_finding /
|
|
// patrol_propose_observer). The legacy direct-resolve alias is deliberately
|
|
// absent: all existing-finding closure crosses the assessed lifecycle.
|
|
ProfilePatrolDetection
|
|
// ProfilePatrolInvestigation is the Patrol investigation posture:
|
|
// non-interactive and structurally read-only - no infrastructure and
|
|
// no Pulse-state mutations. Typed remediation leaves the profile only
|
|
// as a side-effect-free, mutation-none action proposal.
|
|
ProfilePatrolInvestigation
|
|
)
|
|
|
|
// Valid reports whether the profile is one of the closed set. Like the
|
|
// invocation-class vocabulary, execution profiles are a closed
|
|
// vocabulary: an unknown value must be rejected, never silently treated
|
|
// as the permissive interactive default.
|
|
func (p ExecutionProfile) Valid() bool {
|
|
switch p {
|
|
case ProfileInteractiveAssistant, ProfilePatrolDetection, ProfilePatrolInvestigation:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
// NonInteractive reports whether the profile forbids interactive user
|
|
// input. This is deliberately independent of autonomous mode: suppressing
|
|
// questions grants no mutation authority, and mutation policy never
|
|
// loosens because a run is interactive. Unknown profiles are treated as
|
|
// non-interactive: an unclassifiable posture must never gain the
|
|
// interactive default's permissions.
|
|
func (p ExecutionProfile) NonInteractive() bool {
|
|
return p != ProfileInteractiveAssistant
|
|
}
|
|
|
|
// patrolDetectionPulseStateAllowlist names the only tools whose
|
|
// pulse-state mutations the detection profile permits. A blanket
|
|
// pulse-state allowance would also permit alert dismissal and knowledge
|
|
// writes, which detection has no business performing.
|
|
func patrolDetectionPulseStateAllowlist() map[string]bool {
|
|
return map[string]bool{
|
|
agentcapabilities.PatrolAssessFindingToolName: true,
|
|
agentcapabilities.PatrolProposeObserverToolName: true,
|
|
agentcapabilities.PatrolReportFindingToolName: true,
|
|
}
|
|
}
|
|
|
|
// ApplyExecutionProfile applies the profile's policy to this executor
|
|
// instance (normally a request-scoped clone). Both Patrol profiles deny
|
|
// infrastructure mutations, clear any inherited autonomous mode, and mark
|
|
// the executor non-interactive; they differ only in pulse-state policy.
|
|
// Unknown profiles are rejected outright (panic on programmer error)
|
|
// rather than falling through to the interactive default's permissions.
|
|
func (e *PulseToolExecutor) ApplyExecutionProfile(profile ExecutionProfile) {
|
|
if !profile.Valid() {
|
|
panic(fmt.Sprintf("unknown execution profile %d: profiles are a closed vocabulary and cannot default to interactive permissions", int(profile)))
|
|
}
|
|
e.executionProfile = profile
|
|
switch profile {
|
|
case ProfilePatrolDetection:
|
|
e.isAutonomous = false
|
|
e.denyInfrastructureMutations = true
|
|
e.pulseStateAllowlist = patrolDetectionPulseStateAllowlist()
|
|
case ProfilePatrolInvestigation:
|
|
e.isAutonomous = false
|
|
e.denyInfrastructureMutations = true
|
|
e.pulseStateAllowlist = map[string]bool{}
|
|
default:
|
|
e.denyInfrastructureMutations = false
|
|
// Interactive Assistant is conversation/read/session authority.
|
|
// Finding and knowledge lifecycle writes remain explicit product/API
|
|
// operations and are never model-invokable.
|
|
e.pulseStateAllowlist = map[string]bool{}
|
|
}
|
|
}
|
|
|
|
// ExecutionProfile returns the profile applied to this executor instance.
|
|
func (e *PulseToolExecutor) ExecutionProfile() ExecutionProfile {
|
|
if e == nil {
|
|
return ProfileInteractiveAssistant
|
|
}
|
|
return e.executionProfile
|
|
}
|