package ports import ( "context" "time" "github.com/dtoro/oikos/internal/core/domain" ) // SignalUpsertInput is the observe-pass signal write: open/resolve // transitions with their triggers, committed atomically. type SignalUpsertInput struct { Signal domain.Signal Triggers []SignalTrigger Audit []AuditEntry Event *Event } // SignalTrigger is an automatic follow-up fired on a signal transition // (payload firm; shapes firm up with the Phase 5 observation slice). type SignalTrigger struct { Kind string EntityID domain.UUID Parameters map[string]any } // SignalTransitionInput acks/resolves/mutes a signal (check-then-act on the // signal's current state inside the transaction). type SignalTransitionInput struct { SignalID domain.UUID Action string // "ack", "resolve", "mute" Note string MuteFor time.Duration Actor string Audit []AuditEntry Event *Event } // SignalRepository is the signal aggregate. type SignalRepository interface { Open(ctx context.Context) ([]domain.Signal, error) History(ctx context.Context, entityID domain.UUID, limit int) ([]domain.Signal, error) UpsertWithTriggers(ctx context.Context, input SignalUpsertInput) error Transition(ctx context.Context, input SignalTransitionInput) (domain.Signal, error) } // ExecutionSubmitInput is the queued-execution write: execution row, // approval (for gated risk classes), audit, and event — one transaction. type ExecutionSubmitInput struct { Execution domain.Execution Approval *domain.Approval Audit []AuditEntry Event *Event } // ExecutionCompleteInput closes out an execution: final status, output // summary, audit, event. type ExecutionCompleteInput struct { ExecutionID domain.UUID Status string Output string ExitCode int Audit []AuditEntry Event *Event } // ExecutionRepository is the execution aggregate. Claim uses an advisory // lock so exactly one worker claims a queued execution. type ExecutionRepository interface { List(ctx context.Context, cursor string, limit int) ([]domain.Execution, error) ReadLog(ctx context.Context, executionID domain.UUID) ([]string, error) SubmitQueued(ctx context.Context, input ExecutionSubmitInput) (domain.Execution, error) Claim(ctx context.Context) (*domain.Execution, error) AppendLog(ctx context.Context, executionID domain.UUID, chunk string) error Complete(ctx context.Context, input ExecutionCompleteInput) error } // ApprovalDecideInput verifies the HMAC token (check-then-act), flips the // approval, un-gates the execution, and appends audit — one transaction. // Double-approve must not double-execute. type ApprovalDecideInput struct { ApprovalID domain.UUID Token string Approved bool Actor string Audit []AuditEntry Event *Event } // ApprovalRepository is the approval aggregate. type ApprovalRepository interface { ListPending(ctx context.Context, entityID domain.UUID, limit int) ([]domain.Approval, error) Decide(ctx context.Context, input ApprovalDecideInput) (domain.Approval, error) } // GovernanceStore is the policy read model: the facts the gating decision // needs that live outside the command text — session plan state, assent/ // destructive windows, duplicate pending executions, and target entity // attributes. Implemented by the postgres adapter; faked in portstest. type GovernanceStore interface { // SessionHasPlan reports whether the session has any non-replaced plan // step (the plan-first gate). Fails open on store errors — a flake // must not block otherwise-valid work. SessionHasPlan(ctx context.Context, sessionID string) bool // AssentWindowActive reports a live operator-assent grant for this // agent+session (config_mutation auto-run scope). AssentWindowActive(ctx context.Context, agentID domain.UUID, sessionID string) bool // DestructiveWindowActive reports a live explicitly-confirmed // destructive grant for this agent+target+session. DestructiveWindowActive(ctx context.Context, agentID domain.UUID, targetSlug, sessionID string) bool // PendingApprovalCount counts this session's executions stuck at // pending_approval (the approval-flood gate). PendingApprovalCount(ctx context.Context, sessionID string) int // PendingDuplicate returns the execution id of an identical command // already queued for approval on this target, if any. PendingDuplicate(ctx context.Context, targetID domain.UUID, action string) (domain.UUID, bool) // EntityAttributes loads one entity's attributes map (VM QEMU-guest- // agent state and similar gating inputs). EntityAttributes(ctx context.Context, targetID domain.UUID) (map[string]any, error) } // CreateRunInput records one `run` submission in the execution ledger: // the execution entity + row, its graph edges, the classification // decision, and the audit entry — one transaction. Written before the // route executes (auto-run or queue), exactly as the pre-service // classifyAndGate path did. type CreateRunInput struct { ExecutionID domain.UUID TargetID domain.UUID TargetSlug string AgentID domain.UUID SessionID string Command string Purpose string Action string // "run:{json params}" action column value RiskClass string Route string // "auto-act" | "escalate" ClassReasoning []byte CorrelationID string } // QueueApprovalInput records the operator-approval ask for a gated // execution: the approval row, the execution status flip, and the // session's awaiting_input transition (with its task.status event). type QueueApprovalInput struct { ExecutionID domain.UUID TargetID domain.UUID TargetSlug string Action string Params string RiskClass string SessionID string } // FinalizeExecutionInput closes an auto-run execution with full timing. type FinalizeExecutionInput struct { ExecutionID domain.UUID Status string // "completed" | "failed" Result []byte StartedAt time.Time } // ExecutionRecorder persists the run-submission lifecycle. The service // owns ordering and the decision; the adapter owns the SQL (entity row, // executions row, edges, classifications, approvals, audit, events). type ExecutionRecorder interface { CreateRun(ctx context.Context, input CreateRunInput) error MarkRunning(ctx context.Context, executionID domain.UUID) error Finalize(ctx context.Context, input FinalizeExecutionInput) error QueueApproval(ctx context.Context, input QueueApprovalInput) error }