deferpolicy

package
v0.17.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 13, 2026 License: Apache-2.0 Imports: 5 Imported by: 0

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`.

const (
	DecisionApprove Decision = "approve"
	DecisionReject  Decision = "reject"
	DecisionTimeout Decision = "timeout" // set only by the internal timer
)

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 New

func New() *Engine

New constructs an empty Engine.

func (*Engine) Peek

func (e *Engine) Peek(taskID string) (*Handle, bool)

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

func (e *Engine) Pending() int

Pending returns the count of currently-pending deferrals. Used by runtime health checks and startup logs.

func (*Engine) Register

func (e *Engine) Register(taskID, tool string, spec Spec) (*Handle, error)

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) Deadline

func (h *Handle) Deadline() time.Time

Deadline returns the absolute time the timeout auto-DENYs.

func (*Handle) Spec

func (h *Handle) Spec() Spec

Spec returns the deferral parameters. Audit event source.

func (*Handle) TaskID

func (h *Handle) TaskID() string

TaskID returns the task the deferral is for. Used by the runner to update task status.

func (*Handle) Tool

func (h *Handle) Tool() string

Tool returns the tool name being deferred.

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 email
	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
}

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL