Skip to documentation
Documentation navigation

Documentation navigation

Documentation / guides

Permission review

Add bounded classifier review without transferring gate authority.

developer

Permission review is an optional, asynchronous classifier path beside a live permission gate. A classifier can recommend a one-shot approval, but it cannot deny a request, write reusable rules, mint grants, or close a gate directly. The session may act only after the local review policy, all applicable classifiers, evidence boundaries, observation rechecks, and current gate basis agree.

Classifier contract

gate.PermissionClassifier is the exact trusted implementation interface:

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

NewPermissionClassifierSet creates an immutable ordered registry. Names and revisions must be valid, unique, and bounded to 128 bytes; the Hustle definition must be blocking, named-model, structured-output-with-tools, and carry matching policy, output, and evidence-tool revisions/digests. PermissionClassifierValidationError reports PermissionClassifierInvalid or PermissionClassifierDuplicate plus the registration index. PermissionClassifierPanicError redacts panics from MarshalInput and ValidateResult with PermissionClassifierPanicMarshalInput or PermissionClassifierPanicValidateResult.

Subject and basis

gate.ReviewBasis binds one assessment to the session-owned identity and policy revisions:

FieldType
GateIDgate.ID
ToolExecutionIDgate.ID
SubjectDigest[32]byte
ContextRevisionstring
GatePolicyRevisionstring
ClassifierRevisionstring
SecurityCeilingstring

PermissionReviewSubject contains {Basis ReviewBasis; Request tool.Request; Context ReviewContext}. NewPermissionReviewSubject validates and clones the request/context, computes the subject digest, and rejects mismatch, missing IDs, invalid context, oversized requirements/candidates, and overlong input. Clone owns nested slices; SubjectDigest recomputes the digest without trusting the stored value.

ReviewContext contains identity.Coordinates, context and gate policy revisions, workspace root, working directory, retry reason, security ceiling, bounded authority-labeled entries, and a ReviewTruncation summary. Entries use closed origin values user, assistant, tool, runtime, external, and omission, and closed kinds such as user_message, assistant_tool_request, tool_result, and runtime_context. BuildReviewContext requires a user message and active assistant tool request and marks material truncation explicitly.

Outcomes

PermissionAssessment is {Basis; Risk; Authorization; Categories; Recommendation; Rationale}. Risk is low, medium, high, or critical; authorization is unknown, low, medium, or high; recommendation is allow or needs_human. The 14 closed categories are data exfiltration, credential access, credential probing, destructive local, destructive shared, persistent security weakening, production mutation, protected source control, untrusted code execution, mutable network, prompt injection, authorization conflict, target ambiguity, and insufficient evidence.

PermissionAssessmentOutcome is {Subject; Applicable bool; Status ReviewStatus; Assessment; Observations []ObservationRequirement}. A non-applicable classifier must use ReviewStatusNotApplicable. Only ReviewStatusAllowed carries an assessment or observations; other statuses include needs_human, timed_out, failed, cancelled, and stale.

CombinePermissionAssessments requires exactly one outcome per registered classifier, in registration order, with the same common subject digest and policy revision. Non-applicable outcomes are neutral only with not_applicable. Every applicable outcome must be eligible under the local policy; the first applicable failure reason wins. No applicable classifier returns ReviewDecisionNoApplicableClassifier.

Auto-approval boundary

StartPermissionReview starts after the permission gate is durably activated and returns promptly; classifier work is asynchronous. It records a session-owned review basis. If all required classifiers return eligible, the private session method constructs the only allowed classifier response, ResponseFromClassifier plus ApprovalApprove. Public Session.RespondGate rejects that source, and the classifier has no API for Approve always for this workspace.

%%{init: {"theme":"dark"}}%%
flowchart TD
    A[Permission gate activated] --> B[Capture immutable subject and basis]
    B --> C[Run ordered classifiers and evidence]
    C --> D[Combine outcomes]
    D -->|not eligible, failed, stale, or no applicable| H[Keep human gate open]
    D -->|eligible| E[Recheck observations]
    E -->|mismatch or error| H
    E --> F[Compare current basis while claiming gate]
    F -->|drift or race| H
    F -->|same basis| G[Private one-shot Approve]

The local review path never persists reusable rules or bypasses the evaluator’s execution-bound grant issuance. The human can still answer while review runs; a human response claims the gate first and cancels review.

Stale and breaker

The session compares ToolExecutionID, ContextRevision, SecurityCeiling, and GatePolicyRevision from the live entry with the assessment basis, and rereads the current policy revision under the same claim lock. Drift, a prior human answer, or a missing gate is an expected stale race and returns no session fault. An observation verifier runs outside the lock because it may perform I/O; a mismatch is also a stale no-op.

Circuit-breaker limits are configured with rig.PermissionReviewLimits and rig.WithPermissionReviewLimits. The fields are MaxConsecutiveNeedsHuman, MaxInvalidOrFailed, MaxIdenticalSubjects, MaxStaleResponses, InterruptOnTrip, plus the same four numeric fields in PermissionReviewSessionLimits. The default threshold is DefaultPermissionReviewBreakerThreshold = 20 when classifiers are configured. A turn or session trip disables further automatic reviews and leaves gates human-only.

Source contracts are pkg/gate/reviewer.go, pkg/gate/review_subject.go, and pkg/gate/review_policy.go. Race and audit proofs are internal/sessionruntime/review_race_test.go, internal/sessionruntime/review_state_test.go, and internal/sessionruntime/review_audit_test.go.

Source and proof

← back to documentation