gate

package
v0.26.0 Latest Latest
Warning

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

Go to latest
Published: Aug 14, 2026 License: Apache-2.0 Imports: 20 Imported by: 0

README

pkg/gate

pkg/gate is the harness's generic access-decision layer. It defines the durable domain envelope for human- and policy-resolved gates and the generic three-state access evaluator that decides one typed prepared request per tool call.

It is deliberately generic. pkg/gate:

  • does not parse tool arguments — tools prepare typed requests (tool.CallPreparer in pkg/tool) before evaluation ever starts;
  • does not define sandbox profiles and does not import a sandbox or any other enforcement package — the access, rule, and grant seams are structural, built-in-typed interfaces an enforcing consumer satisfies without importing harness;
  • does not implement a permission-file format — durable rule matching and persistence are consumer-provided (RuleMatcher, RuleWriter).

What is gate?

A gate.Evaluator decides one prepared tool.Request per tool call:

  • Deny — the call is not executed; the model sees a paired permission-denied tool result.
  • Gated — the call needs approval. The whole unmet set is resolved by one combined prompt with exactly three actions: Approve (once), Approve always for this workspace (persists the displayed candidates atomically before any grant is minted), Deny (no error; nothing minted).
  • Allow — the call runs; no grant token is needed from this layer.

Construction explicitly selects the interaction mode:

  • NewInteractiveEvaluator(bindings, matcher, approver, writer, issuer) — requires both an Approver and a durable RuleWriter, so all three approval actions are honest.
  • NewHeadlessEvaluator(bindings, matcher, issuer) — accepts neither, never prompts, and resolves an unmet gated requirement as a typed approval-required denial (EvaluationApprovalRequired).

The package also defines the durable gate envelope (Gate, Payload, GateResponse, GateRoute, ID, Answer, CloseReason) used by every kind of host-facing gate — permission, ask-user, form, open-URL — and the response routing the session uses to deliver a human's reply back to the loop that opened one.

Boundary of responsibilities

Concern Owner
Argument decoding, normalization (commands, URLs, paths), canonical resource identity, per-call artifacts The tool, via tool.CallPreparer.PrepareCall (pkg/tool)
Three-state decision (Deny/Gated/Allow), deny-before-allow ordering, one combined approval, response transport, redacted audit pkg/gate (this package)
Access profiles, OS confinement, grant-token minting and enforcement The enforcing consumer (e.g. a sandbox module), behind the structural AccessSource and GrantIssuer seams
Durable rule storage and matching (whatever file or store format) The consumer, behind RuleMatcher / RuleWriter

Invalid tool input fails during preparation and never reaches the evaluator; tool.ValidateRequest re-checks every prepared-request invariant at the start of evaluation and at both durable codec boundaries.

Typed prepared requests

A tool call is evaluated as one tool.Request: the tool name, a bounded display summary, optional execution-binding fields (ExecutionID, Command, WorkingDirectory, ExpiresAtUnixMilli — required exactly when any requirement requests a grant), and a set of tool.Requirement values. Each requirement carries:

  • Kind — routed to exactly one AccessSource via AccessBindings;
  • Scope — used only for access routing;
  • Match — used only for stored-rule matching;
  • Description — used only for bounded display and audit;
  • an optional GrantClass/GrantTarget pair requesting one post-decision execution-bound grant;
  • Candidates — the exact reusable allow rules displayed to the user and offered for durable persistence.

The access ABI is versioned (CurrentAccessVersion, currently 1). Sources return the raw uint8 states AccessDeny/AccessGated/AccessAllow; unknown kinds, unknown values, source errors, and version mismatches all fail closed as typed AccessError values.

Evaluator lifecycle

Construction explicitly selects the interaction mode:

  • NewInteractiveEvaluator(bindings, matcher, approver, writer, issuer) — requires both an Approver and a durable RuleWriter, so all three approval actions are honest.
  • NewHeadlessEvaluator(bindings, matcher, issuer) — accepts neither, never prompts, and resolves an unmet gated requirement as a typed approval-required denial (EvaluationApprovalRequired).

Authorize(ctx, request) is the single entry: it runs Evaluate, opens at most one combined approval (interactive construction only, and only when gated requirements remain unmet), applies the chosen action via Resolve, and mints fresh execution-bound grants for the approved call.

Evaluate applies the generic order:

  1. Configured access first. Every requirement is routed to its sole bound source. Any Deny short-circuits: the evaluation returns the combined denied set and nothing further is consulted. Allow needs no grant token from this layer; Gated continues.
  2. Every stored deny before any allow. Each gated requirement is checked against RuleMatcher.MatchesDeny; any match denies the call.
  3. Stored allows. A gated requirement matched by MatchesAllow is met; the rest form one combined unmet set together with every displayed reusable candidate.

Resolve applies exactly one of the three approval actions:

  • Approve — approve once; nothing is persisted.
  • Approve always for this workspace — atomically persists the entire displayed candidate batch in one RuleWriter.WriteRules call before any grant is minted; a persistence failure blocks execution.
  • Deny — an unapproved Resolution with no error; nothing is minted.

Every dependency failure (rule match, approver, writer, issuer) is a typed, fail-closed EvaluationError. An unapproved Resolution with a nil error is a policy or user denial, not a fault.

One combined prompt

Multiple gated requirements never produce serial prompts. The whole unmet set travels in one ApprovalPrompt{Request, Unmet, Candidates}, resolved by the consumer's Approver to exactly one ApprovalAction. ApprovalControls() returns the exact, complete control set — there is no session scope, user-global scope, persistent-deny action, or second capability prompt. A partial saved approval yields one prompt containing only the still-unmet requirements.

Inside a running loop, loop.GateApprover() is the Approver a consumer passes to interactive construction: it resolves each combined prompt through the live loop's per-call approval capability (installed on ctx by the runner) and fails closed outside a live loop call.

Response routing

An interactive approval travels as a durable permission gate:

  • The runner opens a Gate of KindPermission whose private PermissionPayload carries the displayed tool.Request. The payload is validated at both codec boundaries (tool.ValidateRequest on marshal, the strict DecodeRequest on unmarshal), so a malformed or token-bearing record can neither be journaled nor restored.
  • The session routes the human's reply by Route (GateID/LoopID/ToolExecutionID): an approve action becomes command.ApproveToolCall carrying the exact gate.ApprovalAction, a deny becomes command.DenyToolCall. ParseApprovalAction is the single validation source shared by the strict wire decoder (DecodeApprovalAction) and the session route; anything but the three exact actions fails closed.
  • The runner maps the routed command back to the action and hands it to Resolve.

Other gate kinds (KindAskUser, KindForm, KindOpenURL) share the same envelope, payload codec, and response routing; see the type docs in payload.go, form.go, and prompt.go.

Audit behavior

Durable audit records are descriptions only, never tokens:

  • PermissionAudit stores the bounded display descriptions of the approved requirements and — only for a workspace approval, which persists them — of the displayed reusable candidates. Never grant tokens, token material, or raw tool arguments.
  • Resolution.Grants is excluded from JSON (json:"-"): minted tokens travel only through the prepared execution contract (tool.PreparedCall), never a prompt, display, journal, or audit payload.
  • RuleCandidate contains no grant or token material; its GrantClass/GrantTarget describe only the structural enforcement contract a future match must preserve.

Permission review

pkg/gate also owns the neutral, mechanism-level permission-review domain: a durable, secret-free envelope a classifier's assessment travels through, and the local policy that turns a validated assessment into a one-shot gate approval. See docs/plans/2026-07-27-permission-classifier-hustle-design.md for the full design; this section documents the public contract.

The classifiers themselves — prompts, wire codecs, evidence-tool catalogs, and evaluation corpus — live in the separate github.com/looprig/classifiers module, never in Harness. pkg/gate never imports it. A consumer composes a classifier (for example commandsafety.New) and registers it with rig.WithPermissionClassifiers; zero registered classifiers preserves this package's existing Deny/Gated/Allow behavior byte-for-byte — see pkg/rig/README.md for the full composition and enable/disable story.

Enable/disable

Permission review is off unless a consumer explicitly registers at least one PermissionClassifier (NewPermissionClassifierSet) and pairs it with a PermissionReviewPolicy via rig.WithPermissionReviewPolicy — there is no global registry, no implicit default classifier, and no model-facing enable/disable control. A rig with zero classifiers behaves exactly as it did before this feature existed: every gated requirement waits on a human.

Model capability requirements

A registered classifier's underlying model must support tool use, structured output, and structured output combined with tool use (the classifier issues zero or more ordinary evidence-tool calls before returning one strict terminal structured result). A capability mismatch fails the review before inference and — like every other review failure — leaves the human gate open; it never blocks or denies the underlying tool call on its own.

Evidence boundaries

A classifier that needs to gather evidence (read a file, check git status, and so on) never gets ambient access. Consumer composition installs the boundary explicitly through rig.WithPermissionReviewEvidence(access, containment, allowedKinds):

  • EvidenceAccessEvaluator — the plain, non-interactive access seam (AccessFor(tool.Requirement) (uint8, error)) evidence calls are routed through. It never prompts, never touches stored rules, and never mints a grant.
  • EvidenceContainmentVerifier — independently resolves every prepared evidence target (including symlinks) against an EvidenceContainmentPolicy (ReadRoot, SecurityCeiling) and rejects root escape or ambiguous scope. SecurityCeiling always comes from the one review's own frozen basis (rig.WithPermissionReviewSecurityCeiling), never a session-wide constant, so a later review in a long session is checked against its own current ceiling.
  • allowedKinds — the exact tool.Requirement.Kind allowlist a classifier's evidence tools may use. An evidence call outside this set, an unknown access state, or any collaborator error fails closed.

A nil or typed-nil verifier, an invalid policy, or a verifier panic all fail closed. Evidence tools never receive session, gate, rule, grant, mutation, or delegation capabilities (design §13.1/§13.3).

Observation recheck (TOCTOU)

Evidence gathering and gate claiming are not atomic: a target a classifier observed (a file it stat'd, a git ref it resolved) can change between the observation and the eventual auto-approval. ObservationRequirement and EvidenceObservationVerifier (design §13.4) close that window:

  • ObservationRequirement{Target, Token} — one canonical-identity/token pair a target-sensitive evidence tool recorded while it ran. gate never computes or interprets either field; both are entirely tool/consumer-owned.
  • EvidenceObservationVerifier — the consumer-supplied, read-only recheck seam (VerifyEvidenceObservations(ctx, EvidenceContainmentPolicy, []ObservationRequirement) error), installed via rig.WithPermissionReviewObservations(verifier). Immediately before a classifier-originated response claims the gate, every observation the contributing classifier(s) recorded is rechecked; a mismatch or unverifiable target makes the response stale — the human gate stays open, exactly like every other review failure below.

Unlike WithPermissionReviewEvidence, this option is optional even when classifiers and evidence are both configured: a session with no target-sensitive evidence tools has nothing to recheck. If a target-sensitive evidence tool DOES record an observation and no verifier is configured, the recheck fails closed (treated as a mismatch) rather than silently skipping — see rig.WithPermissionReviewObservations's doc comment for the full reasoning. This mechanism narrows, but never replaces, the pre-existing symlink-swap, containment, grant-target, and sandbox checks: the eventual tool still consumes its own originally prepared artifact.

Human fallback

This is the feature's core invariant: every classifier outcome other than an eligible allowed leaves the ordinary human gate exactly as open as it would have been with zero classifiers configured. needs_human, not_applicable, timed_out, failed, cancelled, stale, a capability mismatch, an evidence-policy violation, retry exhaustion, or any other expected or unexpected review failure all resolve to the same place: the human can still answer the same gate, at any point, including while a review is in flight (see "One combined prompt" and "Response routing" above). A classifier can only ever narrow to a single one-shot Approve; it cannot deny, cannot persist a rule, cannot widen a security ceiling, and cannot stop the human from answering first.

Audit and privacy

PermissionReviewStarted and PermissionReviewCompleted (pkg/event/permission_review.go) are enduring, secret-free audit events — but both are event.Internal-visibility, so they must be published through Hub.PublishInternalEventChecked, never the public-only PublishEventChecked path (a session-fault bug from exactly that mismatch was fixed during Phase 6; see the design's implementation-plan addenda for the incident). They carry gate/tool-execution identity, classifier name/revision, and — for Completed — the closed ReviewStatus, ReviewRisk, ReviewAuthorization, categories, and AutoApproved. ReviewStatusAllowed can never carry ReviewRiskCritical; that combination is a durably rejected, globally impossible audit state.

These events deliberately exclude conversation text, commands and raw arguments, file contents, evidence-tool output, prompt text, model output, rationale, credentials, rule data, and grant material. An ephemeral, bounded, sanitized rationale (MaxPermissionReviewRationaleBytes) may reach an authorized live UI as a diagnostic; it is never journaled or replayed.

gate.ResponseFromClassifier is the ResponseSource kind stamped on a classifier-originated GateResolved/PermissionAudit record. Only a private session-runtime method can produce it — a public caller cannot select this provenance — so audit and UI can always distinguish a human, timeout-policy, or classifier approval without ever claiming a human acted when one did not.

Policy tuning

PermissionReviewPolicy (NewPermissionReviewPolicy / DefaultPermissionReviewPolicy) is the local, consumer-owned ceiling applied after a classifier's result has already been validated: a maximum auto-approvable risk, a per-risk minimum authorization floor, an absolute-human category list (categories no authorization can override — data_exfiltration and prompt_injection are absolute-human by design regardless of what a consumer configures), and a material-context-truncation mask. A policy a consumer builds can only be at least as restrictive as Harness's own hard review ceiling; NewPermissionReviewPolicy rejects a looser one.

Independently, rig.WithPermissionReviewLimits(rig.PermissionReviewLimits{...}) tunes the turn- and session-scoped circuit breaker (design §18): 8 thresholds total (4 turn-scoped: MaxConsecutiveNeedsHuman, MaxInvalidOrFailed, MaxIdenticalSubjects, MaxStaleResponses, plus InterruptOnTrip; and the same 4 again, session-scoped, under Session). Each defaults to rig.DefaultPermissionReviewBreakerThreshold (20) when classifiers are configured but this option is never called. These are operational tuning knobs, not behavioral identity, so they are deliberately excluded from the rig fingerprint — two rigs that agree on classifiers and policy but differ only in these thresholds compare equal.

Carbon's internal/app/permission_review.go is a complete real example: it offers a Codex-compatible default policy and a strictly tighter alternative (PermissionReviewStrictPolicy), never the reverse.

Restore behavior

Rig identity (pkg/rig/README.md's "Configuration fingerprint") folds in ordered classifier names/revisions, definition descriptors, the local review policy revision, and the evidence catalog. A ConfigManifest.PermissionReviewConfigured bool tracks whether ANY permission review is configured at all, kept deliberately separate from that opaque topology hash so drift assessment can be directional: going from disabled to enabled on restore is a DriftWarn, which session.DefaultPolicyDecider rejects — a session must never silently start auto-reviewing gates that were 100% human-only when it was opened. A rig accepts that transition only if its consumer opted in, either narrowly via a custom rig.WithRestoreDecider that inspects the assessment and accepts this dimension specifically, or with the older, deliberately blanket rig.WithAllowConfigMismatch() (accepts every Warn-level drift, not just this one — session.RestoreDecider's doc marks it the deprecated predecessor of WithRestoreDecider). Going from enabled to disabled is narrowing and is unaffected (DriftInfo). A same-config restore, or an identity change with review already enabled on both sides, is governed by the ordinary TopologyRev comparison alone. Hustles themselves are never restored: if the process exits mid-review, the gate restores as an ordinary open human gate, no classifier response is synthesized, and no review is rerun from guessed context (design §15).

Evaluation workflow

pkg/gate and pkg/hustle do not run or score evaluations themselves — that lives in the classifiers module's own evaluation corpus and deterministic report runner (commandsafety.Evaluate), which exercises this package's real gate.BuildReviewContext, gate.NewPermissionReviewSubject, and gate.EvaluatePermissionAssessment against synthetic, versioned corpus fixtures. See classifiers/docs/evaluations/ for the corpus format, coverage requirements, and report shape (design §22.6/§22.7).

Example

This example is compiled and run as a doc test (example_test.go); keep the two in sync.

// staticAllow is a minimal AccessSource that allows every routed scope.
type staticAllow struct{}

func (staticAllow) AccessVersion() uint16 { return gate.CurrentAccessVersion }
func (staticAllow) AccessFor(kind, scope string) (uint8, error) {
	return gate.AccessAllow, nil
}

func Example() {
	evaluator, err := gate.NewHeadlessEvaluator(
		[]gate.AccessBinding{{Kind: "fs.read", Source: staticAllow{}}},
		nil, // no stored rules
		nil, // no grant issuer: no requirement below requests a grant
	)
	if err != nil {
		fmt.Println(err)
		return
	}
	resolution, err := evaluator.Authorize(context.Background(), tool.Request{
		ToolName: "Read",
		Requirements: []tool.Requirement{{
			Kind:        "fs.read",
			Match:       "Read(/repo/README.md)",
			Description: "Read /repo/README.md",
		}},
	})
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println(resolution.Approved)
	// Output: true
}

Sibling packages

  • pkg/tooltool.Request, tool.Requirement, tool.RuleCandidate, tool.ValidateRequest, and the CallPreparer boundary that produces the typed request the evaluator consumes.
  • pkg/commandcommand.ApproveToolCall / command.DenyToolCall, the routed wire forms of an ApprovalAction; ParseApprovalAction is the single validation source shared by the strict decoder here and the session route.
  • pkg/eventevent.PermissionRequested carries the gate id the session uses to route a reply.
  • pkg/looploop.AccessGate is the runner's view of an evaluator; loop.GateApprover is the Approver a live loop passes to interactive construction.
  • pkg/hustle — the bounded tool-using loop a permission classifier's evidence gathering runs inside.
  • pkg/rigrig.WithPermissionClassifiers, rig.WithPermissionReviewPolicy, rig.WithPermissionReviewLimits, rig.WithPermissionReviewEvidence, and rig.WithPermissionReviewSecurityCeiling compose everything in "Permission review" above into a running rig.
  • github.com/looprig/sandbox — satisfies AccessSource / GrantIssuer with OS confinement. Harness never imports it.
  • github.com/looprig/classifiers — the classifier product (prompts, wire codecs, evidence-tool catalogs, evaluation corpus) built on this package's public permission-review contracts. Harness never imports it.

Documentation

Overview

Package gate defines the durable domain envelope for human and policy gates and the generic three-state access evaluator.

The evaluator routes each normalized requirement kind to exactly one structural AccessSource, applies deny-before-allow precedence across configured access and stored rules, and combines every unmet gated capability into one approval carrying exactly the Approve, Approve always for this workspace, and Deny actions.

Boundaries are deliberate: this package never imports an enforcement package such as sandbox (access, rule, and grant seams are structural, built-in-typed interfaces), never parses raw tool arguments (tools prepare typed requests; see pkg/tool), and never defines a permission-file format (durable rule matching and persistence are consumer-provided).

Example
package main

import (
	"context"
	"fmt"

	"github.com/looprig/harness/pkg/gate"
	"github.com/looprig/harness/pkg/tool"
)

// This file backs the README example: pkg/gate/README.md §Example mirrors this
// code verbatim. Keep the two in sync.

// staticAllow is a minimal AccessSource that allows every routed scope.
type staticAllow struct{}

func (staticAllow) AccessVersion() uint16 { return gate.CurrentAccessVersion }
func (staticAllow) AccessFor(kind, scope string) (uint8, error) {
	return gate.AccessAllow, nil
}

func main() {
	evaluator, err := gate.NewHeadlessEvaluator(
		[]gate.AccessBinding{{Kind: "fs.read", Source: staticAllow{}}},
		nil, // no stored rules
		nil, // no grant issuer: no requirement below requests a grant
	)
	if err != nil {
		fmt.Println(err)
		return
	}
	resolution, err := evaluator.Authorize(context.Background(), tool.Request{
		ToolName: "Read",
		Requirements: []tool.Requirement{{
			Kind:        "fs.read",
			Match:       "Read(/repo/README.md)",
			Description: "Read /repo/README.md",
		}},
	})
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println(resolution.Approved)
}
Output:
true

Index

Examples

Constants

View Source
const (
	AccessDeny  uint8 = 0
	AccessGated uint8 = 1
	AccessAllow uint8 = 2
)

Access values are fixed by the structural access ABI. AccessSource implementations deliberately return uint8 so Gate need not import an enforcement package's named access type.

View Source
const (
	// FormActionAccept submits answers; Values must satisfy ParseFormAnswers.
	FormActionAccept = "accept"
	// FormActionDecline records an explicit human refusal to answer.
	FormActionDecline = "decline"
	// FormActionCancel records that the request was withdrawn or timed out
	// rather than refused.
	FormActionCancel = "cancel"
)

Form gate response actions. They are the Control.Action / ResponseRequest.Action values a form gate understands, and are answered through the ordinary ResponsePolicy machinery (a PolicyRespond template naming FormActionDecline is the fail-secure default an integration should configure for an unattended form).

View Source
const (
	MaxObservationRequirementTargetBytes    = 4 << 10
	MaxObservationRequirementTokenBytes     = 4 << 10
	MaxObservationRequirementsPerAssessment = 256
)

Bounds on one ObservationRequirement's fields and on how many may travel with a single classifier's PermissionAssessmentOutcome. These are sized like the other bounded string/collection fields in this package (see review_subject.go's MaxPermissionReview* constants) — generous for any real canonical identity or token, small enough that a misbehaving evidence tool cannot use this new channel to smuggle unbounded data through a review.

View Source
const (
	MaxReviewContextInputEntries    = 4096
	MaxReviewContextInputBytes      = 4 << 20
	MaxReviewContextEntryInputBytes = 2 << 20
	MaxReviewContextRootFieldBytes  = 64 << 10
)

Hard input bounds constrain work before any context is encoded.

View Source
const (
	// MaxPermissionReviewRationaleBytes bounds live classifier diagnostics.
	MaxPermissionReviewRationaleBytes = 2048
	// MaxPermissionReviewPolicyRevisionBytes bounds the consumer policy label.
	MaxPermissionReviewPolicyRevisionBytes = 128
	// MaxPermissionClassifierRevisionBytes bounds classifier implementation labels.
	MaxPermissionClassifierRevisionBytes = 128
)
View Source
const (
	MaxPermissionReviewRequestRequirements = 4096
	MaxPermissionReviewRequestCandidates   = 4096
	MaxPermissionReviewRequestStringBytes  = 1 << 20
	MaxPermissionReviewRequestInputBytes   = 1 << 20
)

Hard request-input bounds constrain work before any request is cloned, validated, or projected onto the canonical subject wire.

View Source
const CurrentAccessVersion uint16 = 1

CurrentAccessVersion is the structural access ABI version understood by Gate.

View Source
const CurrentGrantVersion uint16 = 1

CurrentGrantVersion is the structural grant ABI version understood by Gate.

View Source
const MaxPermissionClassifierNameBytes = 128

MaxPermissionClassifierNameBytes bounds the stable audit identity stored in registry and event records.

View Source
const MaxPermissionReviewSubjectWireBytes = 1 << 20

MaxPermissionReviewSubjectWireBytes bounds strict subject decoding before JSON parsing.

View Source
const MaxReviewCategories int = 14

MaxReviewCategories is the maximum number of distinct categories a review may carry. It equals the complete initial closed category domain.

SupportedReviewTruncationMask contains every truncation bit understood by this revision. Callers can identify unknown bits with mask&^SupportedReviewTruncationMask.

Variables

This section is empty.

Functions

func DecodeRequest

func DecodeRequest(data []byte) (tool.Request, error)

DecodeRequest strictly decodes and validates an untrusted prepared request. Unknown fields, duplicate object keys, trailing JSON, null, and invariant violations all fail closed with RequestDecodeError.

func MarshalPayload

func MarshalPayload(payload Payload) ([]byte, error)

MarshalPayload encodes a sealed payload as a {kind,data} discriminator wrapper.

func MarshalResponseAudit

func MarshalResponseAudit(audit ResponseAudit) ([]byte, error)

MarshalResponseAudit encodes a sealed response audit as a {kind,data} wrapper.

func ParseFormAnswers

func ParseFormAnswers(schema PromptSchema, values map[string]json.RawMessage) (map[string]string, error)

ParseFormAnswers validates a FormActionAccept response's Values against the form schema and returns the answers keyed by field name.

It is strict in both directions and fails closed: every submitted value must name a schema field, match that field's JSON type (a string for text and select, a bool for confirm), stay within maxFormValueBytes, and — for a select — name a declared option. Every required field must be present. A schema that does not satisfy ValidateFormSchema is rejected before any value is read, so a caller cannot smuggle an answer past an unvalidated field.

A confirm answer is normalized to "true"/"false".

func SubjectDigest

func SubjectDigest(subject PermissionReviewSubject) ([32]byte, error)

SubjectDigest validates the non-digest subject invariants and recomputes its canonical digest while deliberately ignoring the stored digest.

func ValidateFormAuditBounds

func ValidateFormAuditBounds(audit FormAudit) error

ValidateFormAuditBounds reports whether a FormAudit is small enough to journal.

A form audit records user-authored content verbatim (see FormAudit), and the schema that solicited it is authored by a third party. Bounding it is therefore what keeps "the journal records what the human said" from becoming "a hostile integration can append whatever it likes to the journal". The bounds are the same ones ParseFormAnswers applies on the way in — an answer that passed it always passes this — and they are re-checked at both codec boundaries so a record that was never parsed (a forged or corrupted one) can be neither written nor read back.

func ValidateFormSchema

func ValidateFormSchema(schema PromptSchema) error

ValidateFormSchema reports whether a schema is a well-formed, bounded, answerable form request. It fails closed: an unknown field kind is rejected rather than treated as free text.

func ValidateGate

func ValidateGate(g Gate) error

ValidateGate checks the envelope invariants that a gate's Kind implies. It is called on the open path so an invariant cannot be violated by a caller who simply forgot it.

It is deliberately narrow. Every kind that predates it validates as nil — this is an additive hook, not a retroactive schema check, and it must stay that way unless an existing kind's contract is separately tightened.

Today it enforces two rules, both on open-url gates:

  • It may not be Restorable. OpenURLPayload's action target is never journaled, so a "restored" open-url gate would present a human with an origin and no URL to open — it would fail open into a broken prompt. Restore must instead close it as unavailable and let a live integration mint a fresh request.
  • Prompt.Origin must be a bare origin. The envelope is the only thing a renderer sees, and the origin is what the human's trust decision is made on. Validating it HERE — with the same validateDisplayOrigin the durable payload uses, not a second opinion — is what makes "the envelope's origin is a real origin" a contract a renderer can rely on structurally, rather than a convention like an opener stuffing text into Prompt.Body. An open-url gate with no origin is a prompt that asks a human to authorize an unnamed party, so it is refused rather than rendered.

func ValidateOpenURLPayload

func ValidateOpenURLPayload(p OpenURLPayload) error

ValidateOpenURLPayload reports whether p is a live, well-formed open-url request. It is the open-url counterpart of ValidateFormSchema: the check an opener runs BEFORE a gate exists, so a broken request is refused instead of shown to a human.

Two rules, and both are load-bearing:

  • DisplayOrigin must be a bare, journal-safe origin. This is the same check the codec applies, hoisted to the open path because the codec is not always on it — a session with no journal (the nop appender) would otherwise render whatever an integration passed straight to a human, which is precisely the trust decision the origin exists to inform.
  • URL must be present. A decoded OpenURLPayload always has an empty URL (the action target is never journaled), so an empty URL here means the request is a restored or half-built one with nothing to open. Refusing it is the same fail-closed rule ValidateGate applies to a Restorable open-url gate: an origin with no target is a broken prompt, not a degraded one.

func ValidatePermissionClassifierName

func ValidatePermissionClassifierName(name hustle.Name) error

ValidatePermissionClassifierName applies the stricter canonical name contract shared by permission-classifier registries and durable audit events.

func ValidateReviewCategories

func ValidateReviewCategories(categories []ReviewRiskCategory) error

ValidateReviewCategories validates that categories contains only distinct, known values from the bounded category domain.

Types

type AccessBinding

type AccessBinding struct {
	Kind   string
	Source AccessSource
}

AccessBinding routes one requirement kind to one access source.

type AccessBindings

type AccessBindings struct {
	// contains filtered or unexported fields
}

AccessBindings is a validated, exact-kind routing table.

func NewAccessBindings

func NewAccessBindings(bindings []AccessBinding) (AccessBindings, error)

NewAccessBindings validates that each configured kind has exactly one current access source.

func (AccessBindings) AccessFor

func (b AccessBindings) AccessFor(requirement tool.Requirement) (uint8, error)

AccessFor routes requirement to its sole configured source.

type AccessError

type AccessError struct {
	Kind        AccessErrorKind
	Requirement string
	Cause       error
}

AccessError reports a configuration or source failure while routing access.

func (*AccessError) Error

func (e *AccessError) Error() string

func (*AccessError) Unwrap

func (e *AccessError) Unwrap() error

type AccessErrorKind

type AccessErrorKind string

AccessErrorKind classifies a fail-closed access routing failure.

const (
	AccessKindInvalid        AccessErrorKind = "kind_invalid"
	AccessSourceMissing      AccessErrorKind = "source_missing"
	AccessSourceDuplicate    AccessErrorKind = "source_duplicate"
	AccessSourceNil          AccessErrorKind = "source_nil"
	AccessVersionUnsupported AccessErrorKind = "version_unsupported"
	AccessValueInvalid       AccessErrorKind = "value_invalid"
	AccessSourceFailed       AccessErrorKind = "source_failed"
)

type AccessSource

type AccessSource interface {
	AccessVersion() uint16
	AccessFor(kind, scope string) (uint8, error)
}

AccessSource reports the configured access state for normalized kind/scope pairs. Implementations must fail closed for unknown kinds and malformed scopes.

type Answer

type Answer struct {
	GateID ID
	Action string
	// Values holds form answers keyed by field name. It is nil for any action
	// other than an accepted form.
	Values map[string]string
	Source ResponseSource
}

Answer is the validated result of answering a HOST-OWNED gate, delivered live to the opener that is blocked on it.

It is a LIVE delivery type, not a durable record, and has no JSON codec. A gate answered on behalf of a loop turns into a command the loop consumes in memory; this is the same thing for a gate whose opener is the host itself. What survives the process is the GateResolved event and its FormAudit, which records the same answers durably (see FormAudit) — so the two are separate because they travel differently, not because one hides something from the other.

type ApprovalAction

type ApprovalAction string

ApprovalAction is one of the three exact user-facing permission decisions.

const (
	ApprovalApprove                ApprovalAction = "Approve"
	ApprovalApproveAlwaysWorkspace ApprovalAction = "Approve always for this workspace"
	ApprovalDeny                   ApprovalAction = "Deny"
)

func DecodeApprovalAction

func DecodeApprovalAction(data []byte) (ApprovalAction, error)

DecodeApprovalAction strictly decodes one of the three exact approval actions. Unknown fields, duplicate keys, trailing JSON, and null fail closed.

func ParseApprovalAction

func ParseApprovalAction(s string) (ApprovalAction, bool)

ParseApprovalAction returns the exact approval action named by s. It is the single validation source shared by DecodeApprovalAction and the session gate route; anything but the three exact actions fails closed.

type ApprovalActionDecodeError

type ApprovalActionDecodeError struct{ Cause error }

ApprovalActionDecodeError wraps malformed JSON or a non-exact action.

func (*ApprovalActionDecodeError) Error

func (e *ApprovalActionDecodeError) Error() string

func (*ApprovalActionDecodeError) Unwrap

func (e *ApprovalActionDecodeError) Unwrap() error

type ApprovalPrompt

type ApprovalPrompt struct {
	Request    tool.Request
	Unmet      []tool.Requirement
	Candidates []tool.RuleCandidate
}

ApprovalPrompt is the single combined user-facing approval for one prepared request: the request being decided, every unmet gated requirement, and every reusable candidate displayed for persistence. It never carries tokens.

type Approver

type Approver interface {
	RequestApproval(ctx context.Context, prompt ApprovalPrompt) (ApprovalAction, error)
}

Approver resolves one combined approval prompt to exactly one of the three approval actions. It is consulted at most once per authorized call, only by an interactively constructed evaluator, and any error fails closed.

type AskUserAudit

type AskUserAudit struct {
	AnswerPreview string `json:"answer_preview,omitempty"`
}

AskUserAudit stores a redacted preview of a user answer.

type AskUserPayload

type AskUserPayload struct {
	Question string   `json:"question,omitempty"`
	Choices  []string `json:"choices,omitempty"`
}

AskUserPayload carries an explicit question and optional fixed choices.

type Blocks

type Blocks string

Blocks names the execution scope held while a gate is open.

const (
	// BlocksToolCall means the gate blocks a single tool call.
	BlocksToolCall Blocks = "tool_call"
	// BlocksSession means the gate blocks session progress.
	BlocksSession Blocks = "session"
)

type CloseReason

type CloseReason string

CloseReason records why an open gate was closed.

const (
	// CloseAnswered records a direct response.
	CloseAnswered CloseReason = "answered"
	// ClosePolicyResponse records an automatic policy response.
	ClosePolicyResponse CloseReason = "policy_response"
	// CloseAbandoned records that the gate was left unresolved.
	CloseAbandoned CloseReason = "abandoned"
	// CloseOwnerClosed records that the owning resolver closed the gate.
	CloseOwnerClosed CloseReason = "owner_closed"
	// CloseRestoreUnavailable records that a restorable gate could not be restored.
	CloseRestoreUnavailable CloseReason = "restore_unavailable"
)

type Control

type Control struct {
	Action string `json:"action,omitempty"`
	Label  string `json:"label,omitempty"`
}

Control describes an action the resolver may choose for a prompt.

func ApprovalControls

func ApprovalControls() []Control

ApprovalControls returns the exact, complete control set of a combined access-approval prompt. An interactive gate offers exactly these three actions; there is no session scope, user-global scope, persistent-deny action, or second capability prompt.

type Criticality

type Criticality string

Criticality classifies whether a gate must survive restore boundaries.

const (
	// GateCritical marks a gate that must be restored or resolved explicitly.
	GateCritical Criticality = "critical"
	// GateNonCritical marks a gate that may be abandoned across restore.
	GateNonCritical Criticality = "non_critical"
)

type DenialReason

type DenialReason string

DenialReason classifies why an unapproved resolution was not approved. It names gate decision stages only, never who denied or which rule matched.

const (
	DenialUnspecified DenialReason = ""
	DenialStructural  DenialReason = "structural"
	DenialRefused     DenialReason = "refused"
)

type DisplayOriginError

type DisplayOriginError struct {
	// contains filtered or unexported fields
}

DisplayOriginError reports a DisplayOrigin that is not a bare, journal-safe origin.

func (*DisplayOriginError) Error

func (e *DisplayOriginError) Error() string

type Effect

type Effect string

Effect identifies what resolving the gate does to execution.

const (
	// EffectResume resumes work that was parked on the gate.
	EffectResume Effect = "resume"
	// EffectInitiate starts follow-up work from the gate response.
	EffectInitiate Effect = "initiate"
	// EffectControl applies a control-plane response.
	EffectControl Effect = "control"
)

type Evaluation

type Evaluation struct {
	Denied     []tool.Requirement
	Unmet      []tool.Requirement
	Candidates []tool.RuleCandidate
	// contains filtered or unexported fields
}

Evaluation is the result of evaluating one complete prepared request. Denied and Unmet are combined sets in original request order. Candidates contains every reusable candidate displayed for the unmet set.

type EvaluationError

type EvaluationError struct {
	Kind        EvaluationErrorKind
	Requirement string
	Cause       error
}

EvaluationError reports a dependency failure during evaluation.

func (*EvaluationError) Error

func (e *EvaluationError) Error() string

func (*EvaluationError) Unwrap

func (e *EvaluationError) Unwrap() error

type EvaluationErrorKind

type EvaluationErrorKind string

EvaluationErrorKind classifies a fail-closed evaluation dependency failure.

const (
	EvaluationRuleMatchFailed EvaluationErrorKind = "rule_match_failed"
	// EvaluationDenied is retained for source compatibility.
	// Deprecated: configured, stored, and approval denials return an unapproved Resolution.
	EvaluationDenied                  EvaluationErrorKind = "denied"
	EvaluationActionInvalid           EvaluationErrorKind = "action_invalid"
	EvaluationApproverMissing         EvaluationErrorKind = "approver_missing"
	EvaluationApprovalRequired        EvaluationErrorKind = "approval_required"
	EvaluationApprovalFailed          EvaluationErrorKind = "approval_failed"
	EvaluationWriterMissing           EvaluationErrorKind = "writer_missing"
	EvaluationWriteFailed             EvaluationErrorKind = "write_failed"
	EvaluationIssuerMissing           EvaluationErrorKind = "issuer_missing"
	EvaluationGrantVersionUnsupported EvaluationErrorKind = "grant_version_unsupported"
	EvaluationGrantFailed             EvaluationErrorKind = "grant_failed"
)

type Evaluator

type Evaluator struct {
	// contains filtered or unexported fields
}

Evaluator combines structural access, durable rules, approval persistence, and post-decision grant issuance without importing an enforcement package.

Construction explicitly selects interactive or headless interaction: NewInteractiveEvaluator requires both an approver and a durable rule writer so all three approval actions are honest, while NewHeadlessEvaluator accepts neither, never prompts, and resolves an unmet gated requirement as a typed approval-required denial.

func NewHeadlessEvaluator

func NewHeadlessEvaluator(bindings []AccessBinding, matcher RuleMatcher, issuer GrantIssuer) (*Evaluator, error)

NewHeadlessEvaluator constructs a headless evaluator. Headless construction accepts no approver and no rule writer, never prompts, and exposes no interactive actions: a gated requirement with no compatible saved rule resolves to a typed approval-required denial.

func NewInteractiveEvaluator

func NewInteractiveEvaluator(bindings []AccessBinding, matcher RuleMatcher, approver Approver, writer RuleWriter, issuer GrantIssuer) (*Evaluator, error)

NewInteractiveEvaluator constructs an interactive evaluator. Interactive construction requires both an approver and a durable rule writer so all three approval actions (Approve, Approve always for this workspace, Deny) are honest; a missing approver or writer fails construction.

func (*Evaluator) Authorize

func (e *Evaluator) Authorize(ctx context.Context, request tool.Request) (Resolution, error)

Authorize runs one complete prepared request through the combined gate: it evaluates once, opens at most one approval (interactive construction only, and only when gated requirements remain unmet), resolves the chosen action, and mints fresh execution-bound grants for the approved call.

A configured or stored deny, and an interactive Deny action, return an unapproved Resolution with no error and mint nothing. A headless evaluator with unmet requirements returns a typed approval-required denial. Any dependency or approver failure is a fail-closed error.

func (*Evaluator) Evaluate

func (e *Evaluator) Evaluate(ctx context.Context, request tool.Request) (Evaluation, error)

Evaluate resolves every access state, then every stored deny, then every stored allow. It never serializes approval gates: all unmatched gated requirements are returned together as one combined unmet set.

func (*Evaluator) Interactive

func (e *Evaluator) Interactive() bool

Interactive reports whether this evaluator was interactively constructed.

func (*Evaluator) Resolve

func (e *Evaluator) Resolve(ctx context.Context, evaluation Evaluation, action ApprovalAction) (Resolution, error)

Resolve applies one exact approval action. Workspace approval persists the entire displayed candidate set in one RuleWriter call before any grant is minted; a persistence failure blocks execution. Approve writes nothing. Deny and evaluated denials mint no grants.

type EvidenceAccessEvaluator

type EvidenceAccessEvaluator interface {
	AccessFor(tool.Requirement) (uint8, error)
}

EvidenceAccessEvaluator is the deliberately non-interactive, read-only access seam Harness's evidence-tool runtime (internal/hustleruntime, design §13.1) uses to evaluate one prepared evidence Requirement's configured access state. AccessBindings satisfies it structurally without exposing approval, stored-rule, persistence, or grant capabilities; a consumer's own access source (for example a sandbox access-profile adapter) may implement it directly. It lives in this public package — rather than internal/hustleruntime, which an out-of-module consumer like a rig implementation cannot import — so a consumer can name and implement the interface and its collaborator types (see EvidenceContainmentPolicy) and install them via rig.WithPermissionReviewEvidence.

type EvidenceContainmentPolicy

type EvidenceContainmentPolicy struct {
	ReadRoot        string
	SecurityCeiling string
}

EvidenceContainmentPolicy is the complete security context exposed to an EvidenceContainmentVerifier. ReadRoot must be the canonical workspace root; SecurityCeiling is the effective, non-widenable policy for the ONE review this evidence call belongs to. It is always sourced from that review's own frozen basis (hustle.Request.SecurityCeiling), never a session-wide constant — a long-running session's later review must be bound against ITS OWN current ceiling, not one frozen at session or controller construction.

type EvidenceContainmentVerifier

type EvidenceContainmentVerifier interface {
	VerifyEvidenceContainment(ctx context.Context, policy EvidenceContainmentPolicy, request tool.Request) error
}

EvidenceContainmentVerifier independently resolves every prepared evidence target, including symlinks and ambiguous scopes, against the canonical read root and enforces the configured security ceiling (design §13.1). It receives no session, gate, mutation, grant, rule, or loop-control capability — only the two policy values above and a defensive clone of the normalized prepared request. Implementations must fail closed when a tool-owned Requirement cannot be mapped unambiguously. This is the narrow, trusted-caller seam a consumer implements and installs via rig.WithPermissionReviewEvidence.

type EvidenceObservationVerifier

type EvidenceObservationVerifier interface {
	VerifyEvidenceObservations(ctx context.Context, policy EvidenceContainmentPolicy, requirements []ObservationRequirement) error
}

EvidenceObservationVerifier is the consumer-supplied, read-only seam that rechecks every previously recorded ObservationRequirement immediately before a classifier-originated auto-approval claims the gate (design §13.4, TOCTOU). It mirrors EvidenceContainmentVerifier's shape exactly — same trusted-caller narrowness, same reused EvidenceContainmentPolicy (the canonical read root plus the review's own non-widenable security ceiling are the identical security context both checks need: containment independently re-resolves ONE prepared request at evidence-gathering time, this independently re-resolves EVERY previously recorded target at pre-approval time — the same "resolve fresh, trust nothing captured earlier" contract, just at a different point in the review's lifecycle). A new EvidenceObservationPolicy type was deliberately NOT introduced: the concerns are identical, and duplicating the type would only invite the two to drift.

It receives no session, gate, mutation, grant, rule, or loop-control capability — only the policy and a defensive copy of the requirements to recheck. Implementations must independently re-derive each requirement's current token from its Target and fail closed (return a non-nil error) on any mismatch OR on any target that can no longer be unambiguously resolved/verified — exactly EvidenceContainmentVerifier's own "fail closed when a ... Requirement cannot be mapped unambiguously" requirement, applied to a recheck instead of a first check. This is the narrow, trusted-caller seam a consumer implements and installs via rig.WithPermissionReviewObservations.

type Field

type Field struct {
	Name     string          `json:"name,omitempty"`
	Label    string          `json:"label,omitempty"`
	Kind     FieldKind       `json:"kind,omitempty"`
	Required bool            `json:"required,omitzero"`
	Options  []Option        `json:"options,omitempty"`
	Default  json.RawMessage `json:"default,omitempty"`
}

Field describes one structured input in a prompt schema.

type FieldKind

type FieldKind string

FieldKind identifies the expected shape of a prompt field value.

const (
	// FieldText accepts free-form text input.
	FieldText FieldKind = "text"
	// FieldSelect accepts one value from a fixed option list.
	FieldSelect FieldKind = "select"
	// FieldMultiSelect accepts multiple values from a fixed option list.
	FieldMultiSelect FieldKind = "multi_select"
	// FieldConfirm accepts a boolean confirmation. A schema whose only field is
	// a FieldConfirm is the confirmation-only request.
	FieldConfirm FieldKind = "confirm"
)

type FormAnswerError

type FormAnswerError struct {
	Kind  FormAnswerErrorKind
	Field string
}

FormAnswerError reports a form response that does not satisfy its schema.

func (*FormAnswerError) Error

func (e *FormAnswerError) Error() string

type FormAnswerErrorKind

type FormAnswerErrorKind string

FormAnswerErrorKind classifies a rejected form answer set.

const (
	// FormAnswerUnknownField reports a submitted value naming no schema field.
	FormAnswerUnknownField FormAnswerErrorKind = "unknown_field"
	// FormAnswerMissingRequired reports a required field with no value.
	FormAnswerMissingRequired FormAnswerErrorKind = "missing_required"
	// FormAnswerTypeInvalid reports a value whose JSON type does not match the
	// field kind (a string for text/select, a bool for confirm).
	FormAnswerTypeInvalid FormAnswerErrorKind = "type_invalid"
	// FormAnswerTooLong reports a value exceeding maxFormValueBytes.
	FormAnswerTooLong FormAnswerErrorKind = "too_long"
	// FormAnswerOptionNotAllowed reports a select value outside the field options.
	FormAnswerOptionNotAllowed FormAnswerErrorKind = "option_not_allowed"
)

type FormAudit

type FormAudit struct {
	// Values holds the submitted answers keyed by field name, exactly as
	// ParseFormAnswers produced them (a confirm answer is "true"/"false"). Only
	// names declared by the form's schema appear. JSON object keys marshal in
	// sorted order, so the durable record is stable.
	Values map[string]string `json:"values,omitempty"`
}

FormAudit is the durable record of how a form gate was answered: the answers themselves, keyed by schema field name.

It records USER-AUTHORED CONTENT verbatim, including free text, and that is deliberate. A journal is meant to be the durable truth of a session, and a human's form answer shaped that session as surely as anything they typed into chat — which command.UserInput already records verbatim, block for block. A form answer that reached a durable record only as "a field was answered" would make the journal an incomplete account of its own session. Recording the answer is the norm here; withholding it would be the outlier.

This is NOT a licence to journal secrets, and two controls keep them out:

  • Credential-soliciting fields are refused before a form is ever opened, so the answer to one never exists. That rejection lives with the integration that translates a third-party request into a schema (MCP design §Elicitation); it is the load-bearing control, not this type.
  • An authorization target is not user content and never becomes one. See OpenURLPayload: a URL carrying a PKCE verifier or `state` is structurally excluded from every durable type, and sensitive authorization goes through an open-url gate rather than a form field.

Unredacted is not unbounded. A form schema is authored by a third party, so a hostile or buggy integration must not be able to append an unbounded record to the journal. Values is bounded on BOTH codec boundaries by ValidateFormAuditBounds: at most maxFormFields entries, each name at most maxFormFieldNameBytes and each value at most maxFormValueBytes. Those are the same bounds ParseFormAnswers enforces on the way in, re-checked here so a record that was never parsed cannot be journaled or restored either.

func NewFormAudit

func NewFormAudit(schema PromptSchema, answers map[string]string) FormAudit

NewFormAudit builds the durable audit for answers that already satisfied ParseFormAnswers against schema.

The schema drives the walk (not the answers map), so a field name the schema never declared cannot reach a durable record even if a caller puts one in answers.

type FormAuditError

type FormAuditError struct {
	Kind  FormAuditErrorKind
	Field string
}

FormAuditError reports a form audit whose contents exceed the durable bounds.

func (*FormAuditError) Error

func (e *FormAuditError) Error() string

type FormAuditErrorKind

type FormAuditErrorKind string

FormAuditErrorKind classifies a rejected form audit record.

const (
	// FormAuditTooManyValues reports more than maxFormFields answers.
	FormAuditTooManyValues FormAuditErrorKind = "too_many_values"
	// FormAuditFieldNameTooLong reports an over-long answer field name.
	FormAuditFieldNameTooLong FormAuditErrorKind = "field_name_too_long"
	// FormAuditValueTooLong reports an answer exceeding maxFormValueBytes.
	FormAuditValueTooLong FormAuditErrorKind = "value_too_long"
)

type FormPayload

type FormPayload struct {
	Title  string       `json:"title,omitempty"`
	Body   string       `json:"body,omitempty"`
	Schema PromptSchema `json:"schema,omitzero"`
}

FormPayload carries a bounded, structured human-input request.

It is the AUTHORITATIVE record of what was asked, mirroring how AskUserPayload carries the question that Gate.Prompt merely renders: the Prompt is a presentation projection an opener derives from this payload, while the payload is what a response is validated against (ParseFormAnswers) and what the journal durably records. The two are deliberately not the same field — Prompt is public envelope, the payload is private.

Schema is bounded and restricted to the answerable field kinds; see ValidateFormSchema. It is validated at BOTH codec boundaries, so a malformed schema can neither be journaled nor restored.

type FormSchemaError

type FormSchemaError struct {
	Kind  FormSchemaErrorKind
	Field string
}

FormSchemaError reports a form schema that violates the form contract.

func (*FormSchemaError) Error

func (e *FormSchemaError) Error() string

type FormSchemaErrorKind

type FormSchemaErrorKind string

FormSchemaErrorKind classifies a rejected form schema.

const (
	// FormSchemaEmpty reports a schema with no fields.
	FormSchemaEmpty FormSchemaErrorKind = "schema_empty"
	// FormSchemaTooManyFields reports more than maxFormFields fields.
	FormSchemaTooManyFields FormSchemaErrorKind = "schema_too_many_fields"
	// FormSchemaFieldNameEmpty reports a field with no name.
	FormSchemaFieldNameEmpty FormSchemaErrorKind = "field_name_empty"
	// FormSchemaFieldNameTooLong reports an over-long field name.
	FormSchemaFieldNameTooLong FormSchemaErrorKind = "field_name_too_long"
	// FormSchemaFieldNameDuplicate reports two fields sharing a name.
	FormSchemaFieldNameDuplicate FormSchemaErrorKind = "field_name_duplicate"
	// FormSchemaFieldKindUnsupported reports a field kind a form cannot answer.
	FormSchemaFieldKindUnsupported FormSchemaErrorKind = "field_kind_unsupported"
	// FormSchemaFieldOptionsInvalid reports a select field with no options, too
	// many options, or an empty option value.
	FormSchemaFieldOptionsInvalid FormSchemaErrorKind = "field_options_invalid"
)

type Gate

type Gate struct {
	ID             ID             `json:"id,omitzero"`
	Kind           Kind           `json:"kind,omitempty"`
	Resolver       ResolverKind   `json:"resolver,omitempty"`
	Blocks         Blocks         `json:"blocks,omitempty"`
	Effect         Effect         `json:"effect,omitempty"`
	Criticality    Criticality    `json:"criticality,omitempty"`
	Subject        Subject        `json:"subject,omitzero"`
	Prompt         Prompt         `json:"prompt,omitzero"`
	ResponsePolicy ResponsePolicy `json:"response_policy,omitzero"`
	Restorable     bool           `json:"restorable,omitzero"`
}

Gate is the durable envelope for an open human or policy-resolved gate.

type GateResponse

type GateResponse struct {
	GateID ID                         `json:"gate_id,omitzero"`
	Action string                     `json:"action,omitempty"`
	Values map[string]json.RawMessage `json:"values,omitempty"`
	Source ResponseSource             `json:"source,omitzero"`
}

GateResponse is the resolved response envelope for a gate.

type GateValidationError

type GateValidationError struct {
	Kind     GateValidationErrorKind
	GateKind Kind
	Cause    error
}

GateValidationError reports a gate envelope that violates a kind's invariants.

func (*GateValidationError) Error

func (e *GateValidationError) Error() string

func (*GateValidationError) Unwrap

func (e *GateValidationError) Unwrap() error

type GateValidationErrorKind

type GateValidationErrorKind string

GateValidationErrorKind classifies a rejected gate envelope.

const (
	// GateRestorableNotAllowed reports a gate marked Restorable whose kind can
	// never be restored.
	GateRestorableNotAllowed GateValidationErrorKind = "restorable_not_allowed"
	// GateOriginInvalid reports a gate whose Prompt.Origin is missing or is not
	// a bare origin, on a kind that requires one.
	GateOriginInvalid GateValidationErrorKind = "origin_invalid"
)

type GrantIssuer

type GrantIssuer interface {
	GrantVersion() uint16
	IssueGrant(ctx context.Context, executionID, command, cwd, kind, scope, class, target string, expiryUnixMilli int64) (string, error)
}

GrantIssuer mints one structural, execution-bound grant token. The signature is intentionally dependency-free and is satisfied structurally by an enforcing executor without importing harness.

type ID

type ID = uuid.UUID

ID is the shared gate identifier type. It aliases uuid.UUID so existing text and JSON codecs are preserved exactly.

type Kind

type Kind string

Kind identifies the user-facing gate scenario.

const (
	// KindPermission is a tool permission approval gate.
	KindPermission Kind = "harness.permission"
	// KindAskUser is an explicit user-question gate.
	KindAskUser Kind = "harness.ask_user"
	// KindForm is a structured human-input gate: a bounded set of typed fields
	// (including a confirmation-only field) answered by a human or by policy. It
	// is protocol-neutral — any integration that needs structured human input
	// opens one.
	KindForm Kind = "harness.form"
	// KindOpenURL is a gate asking a human to open an action URL out-of-band
	// (typically a browser) and, when RequiresCompletion is set, to report back.
	//
	// An open-url gate is inherently EPHEMERAL: its action target is bound to a
	// live out-of-band exchange and is deliberately never journaled (see
	// OpenURLPayload), so it can never be restored. ValidateGate rejects an
	// open-url gate marked Restorable.
	KindOpenURL Kind = "harness.open_url"
)

type ModelDecisionPolicy

type ModelDecisionPolicy struct {
	Prompt         string           `json:"prompt,omitempty"`
	AllowedActions []string         `json:"allowed_actions,omitempty"`
	Default        ResponseTemplate `json:"default,omitzero"`
	Metadata       json.RawMessage  `json:"metadata,omitempty"`
}

ModelDecisionPolicy configures a model-assisted gate decision.

type NilPayloadError

type NilPayloadError struct{}

NilPayloadError is returned when MarshalPayload receives a nil payload.

func (*NilPayloadError) Error

func (e *NilPayloadError) Error() string

type NilResponseAuditError

type NilResponseAuditError struct{}

NilResponseAuditError is returned when MarshalResponseAudit receives nil audit data.

func (*NilResponseAuditError) Error

func (e *NilResponseAuditError) Error() string

type ObservationRequirement

type ObservationRequirement struct {
	Target string
	Token  string
}

ObservationRequirement is one canonical-identity/token pair recorded when a target-sensitive evidence tool observed one target during evidence gathering (design §13.4, TOCTOU). Target is the verifier-defined canonical identity of the observed target (for example a canonicalized absolute path or a resolved git ref) — Harness never computes, interprets, or canonicalizes it, for the same reason EvidenceContainmentVerifier's own doc comment gives for why Harness does not own path canonicalization. Token is an opaque, verifier-defined proof of that target's observed state at capture time (for example a hash of stable metadata) — again never computed or interpreted by Harness.

A zero ObservationRequirement is never valid on its own (see Valid); the zero value exists only so the type is usable as an ordinary Go value (comparable, safe to append to a nil slice).

func (ObservationRequirement) Valid

func (o ObservationRequirement) Valid() bool

Valid reports whether both fields are non-empty, valid UTF-8, free of NUL bytes, and within MaxObservationRequirementTargetBytes/TokenBytes. It is the one shape check every producer and consumer of this type shares — evidence_runner.go applies it before ever recording a requirement (a malformed report from a target-sensitive tool is dropped, not retained), and CombinePermissionAssessments applies it again at the outcome boundary (defense in depth: never trust a single call site to have validated untrusted-shaped data).

type OpenPayload

type OpenPayload struct {
	GateID  ID      `json:"gate_id,omitzero"`
	Payload Payload `json:"payload,omitempty"`
}

OpenPayload records a gate opening and its nested scenario payload.

type OpenURLPayload

type OpenURLPayload struct {
	DisplayOrigin string `json:"display_origin,omitempty"`
	// URL is never serialized. See the type doc.
	URL                string `json:"-"`
	RequiresCompletion bool   `json:"requires_completion,omitzero"`
}

OpenURLPayload asks a human to open an action URL out-of-band.

URL is the EPHEMERAL action target. An authorization URL carries secrets — OAuth `state`, a PKCE challenge, one-time codes — so it MUST NOT reach a journal, an event, or an audit record. That exclusion is STRUCTURAL, not remembered: the field is `json:"-"`, and the codec marshals openURLPayloadData, a type that HAS NO URL FIELD at all. Both boundaries have to be deleted for a URL to leak. A decoded OpenURLPayload therefore ALWAYS has an empty URL, which is exactly why an open-url gate may not be Restorable (enforced by ValidateGate) — the action target cannot survive a restore, and a reconnecting integration must mint a fresh one.

DisplayOrigin is the durable, journal-safe origin shown to the human, e.g. "https://github.com". It is validated as a BARE origin (scheme + host only) at both codec boundaries: without that check a caller could defeat the whole design by passing the full action URL as the "origin".

type OpenURLPayloadError

type OpenURLPayloadError struct {
	Kind OpenURLPayloadErrorKind
}

OpenURLPayloadError reports an open-url payload that cannot be acted on.

func (*OpenURLPayloadError) Error

func (e *OpenURLPayloadError) Error() string

type OpenURLPayloadErrorKind

type OpenURLPayloadErrorKind string

OpenURLPayloadErrorKind classifies a rejected open-url payload.

const (
	// OpenURLTargetMissing reports an open-url payload with no action URL.
	OpenURLTargetMissing OpenURLPayloadErrorKind = "target_missing"
)

type Option

type Option struct {
	Value string `json:"value,omitempty"`
	Label string `json:"label,omitempty"`
}

Option is a selectable value for select-style fields.

type Payload

type Payload interface {
	// contains filtered or unexported methods
}

Payload is the sealed union of durable gate payload records.

func UnmarshalPayload

func UnmarshalPayload(data []byte) (Payload, error)

UnmarshalPayload decodes a {kind,data} payload wrapper and fails closed on unknown kinds.

type PayloadDecodeError

type PayloadDecodeError struct {
	Kind  string
	Cause error
}

PayloadDecodeError wraps malformed payload JSON or malformed payload data.

func (*PayloadDecodeError) Error

func (e *PayloadDecodeError) Error() string

func (*PayloadDecodeError) Unwrap

func (e *PayloadDecodeError) Unwrap() error

type PayloadEncodeError

type PayloadEncodeError struct {
	Kind  string
	Cause error
}

PayloadEncodeError wraps failures while encoding a payload wrapper or data.

func (*PayloadEncodeError) Error

func (e *PayloadEncodeError) Error() string

func (*PayloadEncodeError) Unwrap

func (e *PayloadEncodeError) Unwrap() error

type PermissionAssessment

type PermissionAssessment struct {
	Basis          ReviewBasis
	Risk           ReviewRisk
	Authorization  ReviewAuthorization
	Categories     []ReviewRiskCategory
	Recommendation ReviewRecommendation
	Rationale      string
}

PermissionAssessment is a classifier's authority-free recommendation for one exact permission review subject.

type PermissionAssessmentOutcome

type PermissionAssessmentOutcome struct {
	Subject      PermissionReviewSubject
	Applicable   bool
	Status       ReviewStatus
	Assessment   PermissionAssessment
	Observations []ObservationRequirement
}

PermissionAssessmentOutcome is one classifier's applicability and terminal review state. Only applicable, allowed outcomes carry an assessment that can contribute to eligibility.

Observations is the set of ObservationRequirement tokens (design §13.4, TOCTOU) this classifier's OWN evidence gathering recorded — one review may gather evidence about several distinct targets, so this is a slice, not a single value. It is meaningful only when Status is ReviewStatusAllowed (validPermissionAssessmentOutcome requires it empty otherwise, mirroring Assessment's own zero-value-unless-allowed rule): a classifier that never reached an allowed terminal never contributes evidence a response could be approved on, so its observations (if any were gathered before it failed) are simply discarded rather than carried forward.

type PermissionAudit

type PermissionAudit struct {
	RequirementDescriptions []string `json:"requirement_descriptions,omitempty"`
	CandidateDescriptions   []string `json:"candidate_descriptions,omitempty"`
}

PermissionAudit is the durable, redacted record of a permission approval: the bounded display DESCRIPTIONS of the requirements the user approved and — for a workspace approval — of the reusable rule candidates that were displayed for persistence. It never carries grant tokens, token material, or raw tool arguments; descriptions are the only permitted content.

type PermissionClassifier

type PermissionClassifier interface {
	Name() hustle.Name
	Revision() string
	Definition() hustle.Definition
	Applies(PermissionReviewSubject) bool
	MarshalInput(PermissionReviewSubject) (json.RawMessage, error)
	ValidateResult(PermissionReviewSubject, hustle.Result) (PermissionAssessment, error)
}

PermissionClassifier is the deliberately narrow contract implemented by trusted classifier packages. It conveys data and immutable Hustle policy, never gate response or durable-grant authority.

type PermissionClassifierNameValidationError

type PermissionClassifierNameValidationError struct{}

PermissionClassifierNameValidationError reports a rejected classifier audit name without echoing the untrusted value.

func (*PermissionClassifierNameValidationError) Error

type PermissionClassifierPanicError

type PermissionClassifierPanicError struct {
	Method PermissionClassifierPanicMethod
}

PermissionClassifierPanicError is the redacted recovery product for a panic raised by a registered PermissionClassifier implementation. Classifiers are trusted but not infallible: a buggy implementation can still panic, and a panic on the review goroutine would otherwise crash the whole process rather than just fail the one review. This is the bounded internal failure design's error taxonomy calls "callback panic at a trusted boundary" — it deliberately retains no panic value, since the panic could be an error, a string, or anything else, and could itself carry raw classifier- controlled subject content.

func (*PermissionClassifierPanicError) Error

type PermissionClassifierPanicMethod

type PermissionClassifierPanicMethod string

PermissionClassifierPanicMethod identifies which trust-boundary method of a registered PermissionClassifier panicked. It exists only to route a recovered panic into a bounded, content-free error — never to convey the panic value itself.

const (
	PermissionClassifierPanicMarshalInput   PermissionClassifierPanicMethod = "marshal_input"
	PermissionClassifierPanicValidateResult PermissionClassifierPanicMethod = "validate_result"
)

type PermissionClassifierSet

type PermissionClassifierSet struct {
	// contains filtered or unexported fields
}

PermissionClassifierSet is an immutable, ordered classifier registry.

func NewPermissionClassifierSet

func NewPermissionClassifierSet(
	classifiers ...PermissionClassifier,
) (PermissionClassifierSet, error)

NewPermissionClassifierSet validates metadata without executing classifier applicability, serialization, or result parsing behavior.

func (PermissionClassifierSet) Classifiers

Classifiers returns an independent ordered registry view.

type PermissionClassifierValidationError

type PermissionClassifierValidationError struct {
	Index  int
	Reason PermissionClassifierValidationReason
}

PermissionClassifierValidationError reports only a bounded reason and registration position.

func (*PermissionClassifierValidationError) Error

type PermissionClassifierValidationReason

type PermissionClassifierValidationReason string

PermissionClassifierValidationReason is the bounded registry rejection domain. Rejected classifier metadata is never included in the error.

const (
	PermissionClassifierInvalid   PermissionClassifierValidationReason = "invalid"
	PermissionClassifierDuplicate PermissionClassifierValidationReason = "duplicate"
)

type PermissionPayload

type PermissionPayload struct {
	Request tool.Request `json:"request,omitzero"`
}

PermissionPayload carries the typed prepared access request a permission gate decides — the request narrowed to what the approval prompt displayed. It is validated at BOTH codec boundaries (tool.ValidateRequest on marshal, the strict DecodeRequest on unmarshal), so a malformed or token-bearing record can neither be journaled nor restored. It never carries grant tokens: tool.Request has no token field, and unknown wire keys are rejected.

type PermissionReviewPolicy

type PermissionReviewPolicy struct {
	Revision             string
	MaximumAutoRisk      ReviewRisk
	MinimumAuthorization map[ReviewRisk]ReviewAuthorization
	AbsoluteHuman        []ReviewRiskCategory
	MaterialTruncation   ReviewTruncationMask
	// contains filtered or unexported fields
}

PermissionReviewPolicy is the local, consumer-owned ceiling applied after a classifier result has been validated.

func DefaultPermissionReviewPolicy

func DefaultPermissionReviewPolicy(revision string) (PermissionReviewPolicy, error)

DefaultPermissionReviewPolicy constructs the Codex-compatible default: low and medium need no authorization evidence, while high requires medium.

func NewPermissionReviewPolicy

func NewPermissionReviewPolicy(
	revision string,
	maximum ReviewRisk,
	minimum map[ReviewRisk]ReviewAuthorization,
	absoluteHuman []ReviewRiskCategory,
	material ReviewTruncationMask,
) (PermissionReviewPolicy, error)

NewPermissionReviewPolicy validates and owns a policy that is at least as restrictive as Harness's hard review ceiling.

func (PermissionReviewPolicy) Sealed

func (p PermissionReviewPolicy) Sealed() bool

Sealed reports whether policy was constructed through NewPermissionReviewPolicy or DefaultPermissionReviewPolicy, as opposed to a hand-built literal PermissionReviewPolicy{} that never went through either constructor. It is a convenience for a caller that wants to fail fast at its own configuration boundary rather than discovering the same zero seal later: EvaluatePermissionAssessment already fails closed on an unsealed policy regardless of whether a caller checks Sealed() first, so this method adds no new security enforcement — it only reports the existing invariant earlier and with a clearer symptom.

type PermissionReviewSubject

type PermissionReviewSubject struct {
	Basis   ReviewBasis   `json:"basis"`
	Request tool.Request  `json:"request"`
	Context ReviewContext `json:"context"`
}

PermissionReviewSubject is the immutable, authority-labeled input to a permission classifier.

func NewPermissionReviewSubject

func NewPermissionReviewSubject(
	basis ReviewBasis,
	request tool.Request,
	context ReviewContext,
) (PermissionReviewSubject, error)

NewPermissionReviewSubject validates, owns, and digest-stamps one subject.

func (PermissionReviewSubject) Clone

Clone returns an owned copy of the subject and all nested slices.

type PolicyAction

type PolicyAction string

PolicyAction names the automatic behavior to apply when policy resolves a gate.

const (
	// PolicyWait leaves the gate open for an explicit response.
	PolicyWait PolicyAction = "wait"
	// PolicyRespond resolves the gate with a configured response template.
	PolicyRespond PolicyAction = "respond"
	// PolicySuspendSession suspends session progress while the gate remains open.
	PolicySuspendSession PolicyAction = "suspend_session"
	// PolicyModelDecide asks a model decision policy to choose a response.
	PolicyModelDecide PolicyAction = "model_decide"
)

type Prompt

type Prompt struct {
	Title string `json:"title,omitempty"`
	Body  string `json:"body,omitempty"`
	// Origin is the validated bare origin an open-url gate asks a human to
	// authorize, e.g. "https://github.com". It is the security-load-bearing part
	// of such a prompt: it is the thing a human makes the trust decision on, so
	// a renderer must be able to display it AS a validated origin.
	//
	// For a KindOpenURL gate it is REQUIRED and ValidateGate enforces that it is
	// a bare origin — scheme and host, no path, query, fragment, or userinfo —
	// with the same check that guards the durable OpenURLPayload.DisplayOrigin.
	// That is what lets a renderer trust it structurally instead of by
	// convention: an opener cannot smuggle a full action URL (with its `state`
	// and PKCE parameters) into the place a human reads as "who am I trusting".
	//
	// It is NOT the action target. The ephemeral URL lives only on the private
	// OpenURLPayload, reaches no durable record and no renderer, and opening it
	// is the host's job.
	//
	// Other kinds leave it empty.
	Origin   string       `json:"origin,omitempty"`
	Schema   PromptSchema `json:"schema,omitzero"`
	Controls []Control    `json:"controls,omitempty"`
}

Prompt is the user-facing content and schema for resolving a gate.

It is the PUBLIC presentation projection: it travels on the gate envelope (and therefore on event.GateOpened) while the payload stays private to the opener and the session. A renderer sees only this.

Every field a renderer must be able to TRUST — rather than treat as arbitrary text an integration supplied — is derived from the private payload by the session at open time (see the GateHost open path), never taken on the caller's word. Origin and Schema are those fields; Title and Body are prose.

type PromptSchema

type PromptSchema struct {
	Fields []Field `json:"fields,omitempty"`
}

PromptSchema groups the structured fields requested by a prompt.

type RequestDecodeError

type RequestDecodeError struct{ Cause error }

RequestDecodeError wraps malformed request JSON or a decoded request that violates the prepared request invariants.

func (*RequestDecodeError) Error

func (e *RequestDecodeError) Error() string

func (*RequestDecodeError) Unwrap

func (e *RequestDecodeError) Unwrap() error

type Resolution

type Resolution struct {
	Approved          bool         `json:"approved"`
	Grants            []string     `json:"-"`
	Denial            DenialReason `json:"-"`
	DenialDescription string       `json:"-"`
}

Resolution is the live result of applying an approval action. Grants and denial routing metadata are deliberately excluded from JSON: they may only travel through the live authorization path, never a prompt, display, journal, or audit payload.

type ResolverKind

type ResolverKind string

ResolverKind identifies the owner responsible for resolving a gate response.

const (
	// ResolverLoop routes responses to a loop-local gate resolver.
	ResolverLoop ResolverKind = "loop"
	// ResolverSession routes responses through the session gate resolver.
	ResolverSession ResolverKind = "session"
)

type ResponseAudit

type ResponseAudit interface {
	// contains filtered or unexported methods
}

ResponseAudit is the sealed union of durable, redacted gate response audit records.

func UnmarshalResponseAudit

func UnmarshalResponseAudit(data []byte) (ResponseAudit, error)

UnmarshalResponseAudit decodes a {kind,data} wrapper and fails closed on unknown kinds.

type ResponseAuditDecodeError

type ResponseAuditDecodeError struct {
	Kind  string
	Cause error
}

ResponseAuditDecodeError wraps malformed audit JSON or malformed audit data.

func (*ResponseAuditDecodeError) Error

func (e *ResponseAuditDecodeError) Error() string

func (*ResponseAuditDecodeError) Unwrap

func (e *ResponseAuditDecodeError) Unwrap() error

type ResponseAuditEncodeError

type ResponseAuditEncodeError struct {
	Kind  string
	Cause error
}

ResponseAuditEncodeError wraps failures while encoding response audit data.

func (*ResponseAuditEncodeError) Error

func (e *ResponseAuditEncodeError) Error() string

func (*ResponseAuditEncodeError) Unwrap

func (e *ResponseAuditEncodeError) Unwrap() error

type ResponsePolicy

type ResponsePolicy struct {
	// Timeout marshals as a time.Duration integer in nanoseconds.
	Timeout time.Duration `json:"timeout,omitzero"`
	// OnTimeout is the action taken when Timeout elapses.
	OnTimeout PolicyAction `json:"on_timeout,omitempty"`
	// Response is the template submitted through RespondGate for PolicyRespond.
	Response ResponseTemplate `json:"response,omitzero"`
	// ModelDecision configures PolicyModelDecide when a responder exists.
	ModelDecision ModelDecisionPolicy `json:"model_decision,omitzero"`
}

ResponsePolicy describes automatic handling for an unresolved gate.

func (ResponsePolicy) EffectiveAction

func (p ResponsePolicy) EffectiveAction() PolicyAction

EffectiveAction returns the configured timeout action, defaulting to PolicyWait.

type ResponseRequest

type ResponseRequest struct {
	Action string                     `json:"action,omitempty"`
	Values map[string]json.RawMessage `json:"values,omitempty"`
}

ResponseRequest is the generic action and value payload used to answer a gate.

type ResponseSource

type ResponseSource struct {
	Kind   ResponseSourceKind `json:"kind,omitempty"`
	Reason string             `json:"reason,omitempty"`
}

ResponseSource describes the origin and reason for a response.

type ResponseSourceKind

type ResponseSourceKind string

ResponseSourceKind identifies who produced a gate response.

const (
	// ResponseFromUser records a response supplied by a human user.
	ResponseFromUser ResponseSourceKind = "user"
	// ResponseFromPolicy records a response supplied by gate policy.
	ResponseFromPolicy ResponseSourceKind = "policy"
	// ResponseFromModel records a response supplied by model decision policy.
	ResponseFromModel ResponseSourceKind = "model"
	// ResponseFromClassifier records a response the session itself
	// constructed from an eligible automated permission-classifier
	// assessment (design §16.3). Only a private session-runtime method may
	// stamp this source; a public caller's attempt to select it directly is
	// rejected rather than honored (see internal/sessionruntime.RespondGate).
	ResponseFromClassifier ResponseSourceKind = "classifier"
)

type ResponseTemplate

type ResponseTemplate struct {
	Action string                     `json:"action,omitempty"`
	Values map[string]json.RawMessage `json:"values,omitempty"`
}

ResponseTemplate is a reusable response payload for policy-driven resolution.

type ResumeInputPayload

type ResumeInputPayload struct {
	InputID uuid.UUID `json:"input_id,omitzero"`
	Preview string    `json:"preview,omitempty"`
}

ResumeInputPayload records input made available when a gate resumes work.

type ReviewAuthorization

type ReviewAuthorization string

ReviewAuthorization is the closed strength of authorization evidenced by the live review context.

const (
	ReviewAuthorizationUnknown ReviewAuthorization = "unknown"
	ReviewAuthorizationLow     ReviewAuthorization = "low"
	ReviewAuthorizationMedium  ReviewAuthorization = "medium"
	ReviewAuthorizationHigh    ReviewAuthorization = "high"
)

func ParseReviewAuthorization

func ParseReviewAuthorization(value string) (ReviewAuthorization, bool)

ParseReviewAuthorization parses a closed review authorization value.

type ReviewBasis

type ReviewBasis struct {
	GateID             ID       `json:"gate_id"`
	ToolExecutionID    ID       `json:"tool_execution_id"`
	SubjectDigest      [32]byte `json:"subject_digest"`
	ContextRevision    string   `json:"context_revision"`
	GatePolicyRevision string   `json:"gate_policy_revision"`
	ClassifierRevision string   `json:"classifier_revision"`
	SecurityCeiling    string   `json:"security_ceiling"`
}

ReviewBasis binds a classifier decision to one exact live permission request and the policy revisions under which it was reviewed.

type ReviewContext

type ReviewContext struct {
	Coordinates        identity.Coordinates
	ContextRevision    string
	WorkspaceRoot      string
	WorkingDirectory   string
	RetryReason        string
	SecurityCeiling    string
	GatePolicyRevision string
	Entries            []ReviewContextEntry
	Truncation         ReviewTruncation
}

ReviewContext is a bounded live-only snapshot used by permission review.

func BuildReviewContext

func BuildReviewContext(input ReviewContext, policy ReviewContextPolicy) (ReviewContext, error)

BuildReviewContext builds an owned, validated review context snapshot.

func (ReviewContext) Clone

func (c ReviewContext) Clone() ReviewContext

Clone returns a context that does not alias the receiver's entry slice.

type ReviewContextEntry

type ReviewContextEntry struct {
	Origin    ReviewContextOrigin
	Kind      ReviewContextKind
	Content   string
	Truncated bool
}

ReviewContextEntry is an authority-labeled block in a review snapshot.

type ReviewContextKind

type ReviewContextKind string

ReviewContextKind is the semantic kind of one permission-review entry.

const (
	ReviewContextKindUserMessage          ReviewContextKind = "user_message"
	ReviewContextKindAssistantMessage     ReviewContextKind = "assistant_message"
	ReviewContextKindAssistantToolRequest ReviewContextKind = "assistant_tool_request"
	ReviewContextKindToolResult           ReviewContextKind = "tool_result"
	ReviewContextKindRuntimeContext       ReviewContextKind = "runtime_context"
	ReviewContextKindExternalContent      ReviewContextKind = "external_content"
	ReviewContextKindOmission             ReviewContextKind = "omission"
)

func ParseReviewContextKind

func ParseReviewContextKind(value string) (ReviewContextKind, bool)

ParseReviewContextKind parses one exact closed entry kind.

type ReviewContextOrigin

type ReviewContextOrigin string

ReviewContextOrigin is the authority origin of one permission-review entry.

const (
	ReviewContextOriginUser      ReviewContextOrigin = "user"
	ReviewContextOriginAssistant ReviewContextOrigin = "assistant"
	ReviewContextOriginTool      ReviewContextOrigin = "tool"
	ReviewContextOriginRuntime   ReviewContextOrigin = "runtime"
	ReviewContextOriginExternal  ReviewContextOrigin = "external"
	ReviewContextOriginOmission  ReviewContextOrigin = "omission"
)

func ParseReviewContextOrigin

func ParseReviewContextOrigin(value string) (ReviewContextOrigin, bool)

ParseReviewContextOrigin parses one exact closed authority origin.

type ReviewContextPolicy

type ReviewContextPolicy struct {
	Revision             string
	MaxBytes             int
	MaxEstimatedTokens   int
	MaxEntries           int
	MaxUserEntryBytes    int
	MaxAgentEntryBytes   int
	MaxToolEntryBytes    int
	MaxBlockBytes        int
	MaxActiveActionBytes int
}

ReviewContextPolicy specifies deterministic bounds for a review snapshot.

type ReviewDecision

type ReviewDecision struct {
	Eligible bool
	Reason   ReviewDecisionReason
}

ReviewDecision reports only local one-shot eligibility. It deliberately carries neither a gate action nor classifier-provided text.

func CombinePermissionAssessments

func CombinePermissionAssessments(
	policy PermissionReviewPolicy,
	classifiers PermissionClassifierSet,
	outcomes []PermissionAssessmentOutcome,
) ReviewDecision

CombinePermissionAssessments applies ordered conjunctive review semantics against one immutable registered classifier set. Every registered classifier must contribute exactly one outcome in registration order. The first applicable failure wins; non-applicable outcomes are neutral only when their status is exactly not_applicable.

func EvaluatePermissionAssessment

func EvaluatePermissionAssessment(
	policy PermissionReviewPolicy,
	subject PermissionReviewSubject,
	assessment PermissionAssessment,
) ReviewDecision

EvaluatePermissionAssessment validates all public inputs again and applies the local hard ceiling without mutating them.

type ReviewDecisionReason

type ReviewDecisionReason string

ReviewDecisionReason is the closed, non-sensitive explanation for a local review eligibility decision.

const (
	ReviewDecisionEligible               ReviewDecisionReason = "eligible"
	ReviewDecisionInvalidPolicy          ReviewDecisionReason = "invalid_policy"
	ReviewDecisionInvalidAssessment      ReviewDecisionReason = "invalid_assessment"
	ReviewDecisionBasisMismatch          ReviewDecisionReason = "basis_mismatch"
	ReviewDecisionRecommendation         ReviewDecisionReason = "recommendation"
	ReviewDecisionRiskCeiling            ReviewDecisionReason = "risk_ceiling"
	ReviewDecisionAuthorization          ReviewDecisionReason = "authorization"
	ReviewDecisionAbsoluteHuman          ReviewDecisionReason = "absolute_human"
	ReviewDecisionMaterialTruncation     ReviewDecisionReason = "material_truncation"
	ReviewDecisionNoApplicableClassifier ReviewDecisionReason = "no_applicable_classifier"
	ReviewDecisionClassifierStatus       ReviewDecisionReason = "classifier_status"
)

func ParseReviewDecisionReason

func ParseReviewDecisionReason(value string) (ReviewDecisionReason, bool)

ParseReviewDecisionReason parses an exact closed decision reason.

func (ReviewDecisionReason) Valid

func (r ReviewDecisionReason) Valid() bool

Valid reports whether the reason belongs to the closed decision domain.

type ReviewRecommendation

type ReviewRecommendation string

ReviewRecommendation is the classifier's closed recommendation. A classifier can recommend a one-shot allow or defer to the already-open human gate; it cannot deny or create a durable approval.

const (
	ReviewAllow      ReviewRecommendation = "allow"
	ReviewNeedsHuman ReviewRecommendation = "needs_human"
)

func ParseReviewRecommendation

func ParseReviewRecommendation(value string) (ReviewRecommendation, bool)

ParseReviewRecommendation parses a closed review recommendation value.

type ReviewRisk

type ReviewRisk string

ReviewRisk is the classifier's closed risk level for a permission request.

const (
	ReviewRiskLow      ReviewRisk = "low"
	ReviewRiskMedium   ReviewRisk = "medium"
	ReviewRiskHigh     ReviewRisk = "high"
	ReviewRiskCritical ReviewRisk = "critical"
)

func ParseReviewRisk

func ParseReviewRisk(value string) (ReviewRisk, bool)

ParseReviewRisk parses a closed review risk value.

type ReviewRiskCategory

type ReviewRiskCategory string

ReviewRiskCategory is a closed, policy-relevant reason for a review risk.

const (
	ReviewCategoryDataExfiltration            ReviewRiskCategory = "data_exfiltration"
	ReviewCategoryCredentialAccess            ReviewRiskCategory = "credential_access"  // #nosec G101 -- Closed taxonomy label, not a credential.
	ReviewCategoryCredentialProbing           ReviewRiskCategory = "credential_probing" // #nosec G101 -- Closed taxonomy label, not a credential.
	ReviewCategoryDestructiveLocal            ReviewRiskCategory = "destructive_local"
	ReviewCategoryDestructiveShared           ReviewRiskCategory = "destructive_shared"
	ReviewCategoryPersistentSecurityWeakening ReviewRiskCategory = "persistent_security_weakening"
	ReviewCategoryProductionMutation          ReviewRiskCategory = "production_mutation"
	ReviewCategoryProtectedSourceControl      ReviewRiskCategory = "protected_source_control"
	ReviewCategoryUntrustedCodeExecution      ReviewRiskCategory = "untrusted_code_execution"
	ReviewCategoryMutableNetwork              ReviewRiskCategory = "mutable_network"
	ReviewCategoryPromptInjection             ReviewRiskCategory = "prompt_injection"
	ReviewCategoryAuthorizationConflict       ReviewRiskCategory = "authorization_conflict"
	ReviewCategoryTargetAmbiguity             ReviewRiskCategory = "target_ambiguity"
	ReviewCategoryInsufficientEvidence        ReviewRiskCategory = "insufficient_evidence"
)

func ParseReviewRiskCategory

func ParseReviewRiskCategory(value string) (ReviewRiskCategory, bool)

ParseReviewRiskCategory parses a closed review risk category.

type ReviewStatus

type ReviewStatus string

ReviewStatus is the closed terminal status recorded for a permission review.

const (
	ReviewStatusAllowed       ReviewStatus = "allowed"
	ReviewStatusNeedsHuman    ReviewStatus = "needs_human"
	ReviewStatusNotApplicable ReviewStatus = "not_applicable"
	ReviewStatusTimedOut      ReviewStatus = "timed_out"
	ReviewStatusFailed        ReviewStatus = "failed"
	ReviewStatusCancelled     ReviewStatus = "cancelled"
	ReviewStatusStale         ReviewStatus = "stale"
)

func ParseReviewStatus

func ParseReviewStatus(value string) (ReviewStatus, bool)

ParseReviewStatus parses a closed review status value.

type ReviewTruncation

type ReviewTruncation struct {
	Applied        ReviewTruncationMask
	Material       ReviewTruncationMask
	OmittedEntries int
	OmittedBytes   int
}

ReviewTruncation summarizes bounded context loss.

type ReviewTruncationMask

type ReviewTruncationMask uint16

ReviewTruncationMask identifies which context limits were exercised.

const (
	ReviewTruncationUserEntry ReviewTruncationMask = 1 << iota
	ReviewTruncationAssistantEntry
	ReviewTruncationToolEntry
	ReviewTruncationBlock
	ReviewTruncationEntryCount
	ReviewTruncationTotalBytes
	ReviewTruncationEstimatedTokens
	ReviewTruncationActiveAction
)

type ReviewValidationError

type ReviewValidationError struct {
	Field  ReviewValidationField
	Reason ReviewValidationReason
}

ReviewValidationError reports a bounded review validation failure. It deliberately carries no rejected value so untrusted classifier output cannot leak through logs or audit messages.

func (*ReviewValidationError) Error

func (e *ReviewValidationError) Error() string

type ReviewValidationField

type ReviewValidationField string

ReviewValidationField identifies the bounded part of a review value that failed validation.

const (
	ReviewValidationFieldContext       ReviewValidationField = "context"
	ReviewValidationFieldContextEntry  ReviewValidationField = "context_entry"
	ReviewValidationFieldContextPolicy ReviewValidationField = "context_policy"
)
const (
	ReviewValidationFieldBasis   ReviewValidationField = "basis"
	ReviewValidationFieldDigest  ReviewValidationField = "digest"
	ReviewValidationFieldRequest ReviewValidationField = "request"
	ReviewValidationFieldWire    ReviewValidationField = "wire"
)
const (
	ReviewValidationFieldCategories ReviewValidationField = "categories"
)

type ReviewValidationReason

type ReviewValidationReason string

ReviewValidationReason classifies a review validation failure.

const (
	ReviewValidationUnsupported ReviewValidationReason = "unsupported"
	ReviewValidationDuplicate   ReviewValidationReason = "duplicate"
	ReviewValidationTooMany     ReviewValidationReason = "too_many"
)
const (
	ReviewValidationRequired    ReviewValidationReason = "required"
	ReviewValidationInvalid     ReviewValidationReason = "invalid"
	ReviewValidationOutOfBounds ReviewValidationReason = "out_of_bounds"
	ReviewValidationReserved    ReviewValidationReason = "reserved"
)
const ReviewValidationMismatch ReviewValidationReason = "mismatch"

type Route

type Route struct {
	GateID          ID `json:"gate_id,omitzero"`
	LoopID          ID `json:"loop_id,omitzero"`
	ToolExecutionID ID `json:"tool_execution_id,omitzero"`
}

Route carries the identifiers needed to deliver a response to a gate.

type RuleMatcher

type RuleMatcher interface {
	MatchesDeny(context.Context, tool.Requirement) (bool, error)
	MatchesAllow(context.Context, tool.Requirement) (bool, error)
}

RuleMatcher checks independently stored deny and allow rules for one normalized requirement. Evaluator always checks every deny before consulting any allow.

type RuleWriter

type RuleWriter interface {
	WriteRules(context.Context, []tool.RuleCandidate) error
}

RuleWriter atomically persists a complete batch of displayed reusable allow candidates. Returning an error means none of the candidates were persisted.

type Subject

type Subject struct {
	ToolExecutionID ID     `json:"tool_execution_id,omitzero"`
	ToolUseID       string `json:"tool_use_id,omitempty"`
	TurnID          ID     `json:"turn_id,omitzero"`
	StepID          ID     `json:"step_id,omitzero"`
	InputID         ID     `json:"input_id,omitzero"`
}

Subject identifies the work item a gate is about.

type UnknownPayloadKindError

type UnknownPayloadKindError struct {
	Kind string
}

UnknownPayloadKindError is returned when a payload wrapper names no known kind.

func (*UnknownPayloadKindError) Error

func (e *UnknownPayloadKindError) Error() string

type UnknownResponseAuditKindError

type UnknownResponseAuditKindError struct {
	Kind string
}

UnknownResponseAuditKindError is returned when an audit wrapper names no known kind.

func (*UnknownResponseAuditKindError) Error

Jump to

Keyboard shortcuts

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