Documentation
¶
Overview ¶
Package defer implements governance R4c — the DEFER authorization decision.
Where DENY refuses an action outright and STEP_UP asks the caller to re-authenticate, DEFER says "I cannot decide this autonomously — pause the executor, hand this to an external authority (a human on-call, an approvals system), and resume when a decision arrives." The primitive R4c introduces is executor **pause-and- resume**: a goroutine blocks on a decision channel, the A2A task status flips to `deferred` in the store so parallel callers see it, and the goroutine unblocks when either the decisions endpoint resolves the deferral or the configured timeout auto-denies it.
This is in-process only — a Forge restart abandons any pending deferrals (each blocked goroutine's stack disappears). For deployments that need cross-restart persistence, the Engine interface is intentionally the seam: a future "persistent" impl can serialize pending deferrals to disk / DB and rehydrate on startup. See docs/security/defer-decisions.md.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Decision ¶
type Decision string
Decision is the resolution kind. Uppercase enum values map 1:1 to what the operator/approver sends on `POST /tasks/{id}/decisions`.
type Engine ¶
type Engine struct {
// contains filtered or unexported fields
}
Engine coordinates pending deferrals across the runtime.
Thread-safe. All accessors take the internal mutex; the resolution channels are buffered(1) so a resolver never blocks on a slow waiter (the waiter drains the channel before completing).
func (*Engine) Peek ¶
Peek returns whether a deferral is pending for taskID. Used by the decisions endpoint to reject requests targeting non-deferred tasks with a 404.
func (*Engine) Pending ¶
Pending returns the count of currently-pending deferrals. Used by runtime health checks and startup logs.
func (*Engine) Register ¶
Register creates a new pending deferral for the given task and starts the timeout timer. Returns an error when a deferral is already pending for this task ID (the executor shouldn't call Register twice for the same task without an intervening resolve).
On timeout fire, the engine emits a Resolution{Decision: DecisionTimeout} onto the handle's wait channel. Callers of WaitCtx observe the same channel as an explicit resolve.
func (*Engine) Resolve ¶
func (e *Engine) Resolve(taskID string, r Resolution) error
Resolve finalizes the deferral for taskID with the given decision. Idempotent — a second call for the same task is a no-op (returns nil error, no channel send). Returns an error only when no deferral is pending for the task (404-like).
The decisions endpoint calls this on a POST arriving from an approver.
type Handle ¶
type Handle struct {
// contains filtered or unexported fields
}
Handle represents a pending deferral. The executor obtains one via Engine.Await and blocks on WaitCtx. The decisions endpoint obtains one via Engine.LookUp and calls Resolve.
Handle is intentionally opaque to callers — the engine owns its lifecycle including cleanup. Never construct a Handle outside the engine.
func (*Handle) IsApprover ¶ added in v0.18.1
IsApprover reports whether email is authorized to resolve this deferral. An empty allowlist authorizes anyone (pre-#313 behavior). A non-empty allowlist requires a non-empty email that is a member — fail-closed. Comparison is case-insensitive.
func (*Handle) TaskID ¶
TaskID returns the task the deferral is for. Used by the runner to update task status.
func (*Handle) WaitCtx ¶
func (h *Handle) WaitCtx(ctx context.Context) (Resolution, error)
WaitCtx blocks until either a Resolution arrives (via engine.Resolve) or the ctx is cancelled. Returns the Resolution on success; on ctx cancellation returns (Resolution{}, ctx.Err()).
Called by the executor goroutine inside the BeforeToolExec hook.
type Resolution ¶
type Resolution struct {
Decision Decision
Approver string // free-form; typically a user id or name
ApproverEmail string // resolved approver email (#313), when available
Note string // optional operator-supplied justification
At time.Time
}
Resolution is the decision + metadata that unblocks a pending deferral. Emitted onto Handle.wait, consumed by the executor goroutine when it resumes.
type Spec ¶
type Spec struct {
To string
Timeout time.Duration
ContextForApprover string
// Approvers is the per-tool approver allowlist (#313), normalized to
// lowercase emails. Empty → no allowlist (any approver may resolve).
// Non-empty → the decisions endpoint requires the approver's email to
// be present and in this set (fail-closed).
Approvers []string
}
Spec is the deferral parameters carried from a policy hook into the engine. Deliberately a value type (not a pointer) so a caller can safely stash a copy for audit.