Documentation
¶
Overview ¶
Package approval is the Human-In-The-Loop gate for AI-proposed actions. A proposed action is auto-approved (per the engagement's ApprovalMode + the action's RiskClass), or enqueued for a human, or – if undecided past the timeout – failed CLOSED (denied). Every decision is recorded on the append-only audit log, attributed to the deciding human (or the system on a timeout). It does NOT execute anything: it only produces an ApprovalDecision the safety gate consumes.
Index ¶
- type ResumeFunc
- type Service
- func (s *Service) Decide(ctx context.Context, human string, actionID shared.ID, approve bool, ...) (agent.ApprovalDecision, error)
- func (s *Service) Get(ctx context.Context, actionID shared.ID) (agent.ProposedAction, agent.ApprovalDecision, error)
- func (s *Service) Request(ctx context.Context, p agent.ProposedAction) (agent.ApprovalDecision, error)
- func (s *Service) RunSweeper(ctx context.Context, interval time.Duration)
- func (s *Service) SetResumeEnqueuer(f ResumeFunc)
- func (s *Service) SetTransactionRunner(transactions ports.TenantTransactionRunner)
- func (s *Service) SweepAllExpired(ctx context.Context) (int, error)
- func (s *Service) SweepExpired(ctx context.Context, engagementID shared.ID) (int, error)
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type ResumeFunc ¶
ResumeFunc re-drives a suspended session after its pending action was decided (here: timed out). The composition root supplies it (enqueue an orchestrator resume job) so the approval package does NOT import the orchestrator – avoiding the orchestrator→safety→approval cycle.
type Service ¶
type Service struct {
// contains filtered or unexported fields
}
Service runs the approval policy over a durable ApprovalStore.
func NewService ¶
func NewService(store ports.ApprovalStore, audit ports.AuditLogger, clock ports.Clock, mode agent.ApprovalMode, timeout time.Duration) (*Service, error)
NewService validates its deps and returns the approval service. An unknown mode is left as-is (AutoApproves fails safe to manual); a non-positive timeout disables auto-expiry.
func (*Service) Decide ¶
func (s *Service) Decide(ctx context.Context, human string, actionID shared.ID, approve bool, reason string) (agent.ApprovalDecision, error)
Decide records a human's approve/deny (idempotent – a 2nd decision returns ErrConflict), audited under the HUMAN actor.
func (*Service) Get ¶ added in v0.2.0
func (s *Service) Get(ctx context.Context, actionID shared.ID) (agent.ProposedAction, agent.ApprovalDecision, error)
Get returns the immutable proposal and current decision for an existing approval request.
func (*Service) Request ¶
func (s *Service) Request(ctx context.Context, p agent.ProposedAction) (agent.ApprovalDecision, error)
Request returns the current decision for a proposed action. If it was already decided (resume path), that decision is returned. Otherwise: auto-approvable → approved + audited; else enqueued and returned PENDING (the orchestrator suspends; a human Decides).
func (*Service) RunSweeper ¶
RunSweeper periodically sweeps expired approvals until ctx is cancelled (a running binary must call this – fail-closed timeout is otherwise never enforced in prod).
func (*Service) SetResumeEnqueuer ¶
func (s *Service) SetResumeEnqueuer(f ResumeFunc)
SetResumeEnqueuer installs the callback used to re-drive a session after a timeout deny, so the suspended session resumes, sees the denial via the idempotent gate, and fails fast instead of hanging in awaiting_approval forever.
func (*Service) SetTransactionRunner ¶ added in v0.2.0
func (s *Service) SetTransactionRunner(transactions ports.TenantTransactionRunner)
SetTransactionRunner makes Decide atomic. Without it the decision commits first and the audit append is a separate transaction, so an audit failure returns an error to an operator whose approval has already taken effect: the interface reports failure, the agent proceeds, and the retry answers "already decided".
func (*Service) SweepAllExpired ¶
SweepAllExpired sweeps every engagement that currently has a pending approval (the prod timeout sweeper's fan-out). Returns the total expired.
The sweeper runs on a background context with no tenant, so each scope's tenant is bound before its engagement is swept. Everything downstream of that binding (Pending, Decide, the record audit and the resume enqueue) then runs inside one tenant, which is what lets the durable store reach the RLS-protected approval queue at all.