Documentation
¶
Overview ¶
Capability tool-subjects: reserved grant subjects that fold a family of concrete file-tool subjects so one approval authorizes every member.
Simple-compound recognition, the versioned generalized literal-subject form, and generalized matching for the permission system.
Project-declared command list: the token prefixes a project's settings file approves for Auto posture to run without prompting.
Degradation of a failed permission grant to its in-memory form.
Builds and delivers the synthetic deny-reason envelope for denied tool calls.
Destructive program-of-concern classification: the product-authored set of programs whose `<program> :*` Program-scope grant authorizes known destructive subcommands, and the advisory predicate that lets the program-scope confirm warn more strongly. The signal is advisory only — it never blocks or alters a grant; a program absent from the set is treated as non-destructive for display purposes (allow-list of concern, not of safety).
The same predicate has a second, load-time use: a bare declared_commands entry naming a flagged program is rejected. That reuse does not make it a safety allowlist — a bare entry for an unlisted destructive-capable program (bash, sed, find, mv, curl, …) is still accepted.
Parses, validates, and serializes on-disk permission entries.
Real-grant permission matching across direct, literal, and structured-tool subjects with path-scope rules.
Direct-command stage normalization and direct-subject matching for the permission system.
Literal-command canonicalization and byte-equal literal-subject matching for the permission system.
Whole-command splitting: cd-prefix path-scope hint capture, quote-aware pipeline splitting, and direct-vs-literal-vs-rejected routing for the permission system.
Read-only raw-shell classification: the product-authored allowlist of side-effect-free executables and the per-invocation classifier that lets any active mode auto-allow a pure direct pipeline of allowlisted commands. Originally Plan-only, this classification now drives the read-only auto-allow in every mode; Plan additionally denies every other shape without prompting, while Build and Auto edits prompt for it.
Presentation-only authorization-prompt summary: a plain-language explanation of one prompted request, the requested targets it can establish from a closed shell grammar or a prepared tool's lexical arguments, and the request-shape counts both prompt surfaces render above the decision choices. Nothing here reaches matching, grants, events, or execution.
Path-scope cleaning/resolution, descendant matching, and request path-scope derivation.
Shared shell-word tokenizer for permission-subject normalization: splits a command stage into tokens while preserving quote/escape and original-span metadata.
Turn-scoped approval cache: per-turn memoization of permission outcomes.
Index ¶
- Constants
- func AuditSubjectMatches(kind, requestSubject string, requestTokens, requestAssignments []string, ...) bool
- func BoundPromptFact(value string) string
- func BuildBlockedPathDenyEnvelope(target, entry string, floor bool) string
- func BuildDenyEnvelope(userText string) string
- func BuildGuardDenyEnvelope() string
- func BuildModeDenyEnvelope(reason string) string
- func BuildUnattendedDenyEnvelope(reason string) string
- func CacheIdentity(req MatchRequest) any
- func CanonicalHost(host string) (string, error)
- func CanonicalLiteralForm(input string) (string, error)
- func CapabilityForMember(member string) (string, bool)
- func CapabilityMembers(subject string) ([]string, bool)
- func ClassifyDirectTokens(tokens []string) (offered bool, anchorIdx int)
- func ClassifyExceptional(req MatchRequest, mc ModeContext) (authority string, classifiable bool)
- func CleanAbsResolve(home, baseDir, path string) (string, error)
- func ComputeArgumentHash(canonicalLiteralSubject string) string
- func DecodeDirectRequestSubject(requestSubject, commandSubject string) (assignments []string, clearEnvironment, ok bool)
- func DeliverDeny(toolCallID string, envelope string, appender RecordAppender)
- func DerivePathScope(home, cdHint, processCWD, toolCwd, subjectKind string) (string, error)
- func DirectRequestSubject(commandTokens, assignments []string, clearEnvironment bool) (string, error)
- func DirectUnderReadable(fixed []string, boundary string, tailWildcard bool) string
- func EncodeCompoundSubject(wrapped bool, stageSubjects, operators []string) (string, error)
- func EncodeDirectEnvSubject(assignments []string, clearEnvironment bool, subject string) (string, error)
- func EncodeDirectUnderSubject(tokens []string, pathIndex int, boundary string, tailWildcard bool) (string, error)
- func ExactSubject(program string, args []string) string
- func ExceptionalAuthority(tokens []string) (authority string, ok bool)
- func ExpandBlockedEntry(home, raw string) (string, error)
- func IsCapabilitySubject(subject string) bool
- func IsDeeperScope(candidate, current string) bool
- func IsDescendantOrEqual(path, scope string) bool
- func IsDestructiveProgram(program string) bool
- func IsModeDenyEnvelope(envelope string) bool
- func IsRecoverableDeniedBy(deniedBy string) bool
- func LiteralRewriteAsks(stage StageNormalization, runDir string) bool
- func LiteralRunsHg(canonical string) bool
- func LiteralRunsPS(canonical string) bool
- func MarshalEntry(e PermissionEntry) ([]byte, error)
- func MatchesDeclaredCommand(commandTokens, assignments []string, clearEnvironment bool, ...) bool
- func MatchesDirectSubject(commandTokens, assignments []string, clearEnvironment bool, ...) bool
- func MatchesLiteralSubject(canonicalInput string, storedSubject string) bool
- func ParseWritableFolders(raw any, home, projectRoot string) (folders []string, notices []string, err error)
- func PrefixSubject(program string, prefix []string) string
- func ProbeStageReadOnly(tokens []string) bool
- func ProgramSubject(program string) string
- func ReadOnlyShell(req MatchRequest, mc ModeContext) bool
- func RelWithin(base, path string, foldCase bool) (string, bool)
- func ShellPathAsks(b *BlockedPaths, folder string, argv, candidates []string) bool
- func ShellPathCandidates(argv []string, folder, home string) []string
- func ShellQuote(value string) string
- func VisibleControlEscape(value string) string
- type ApprovalCache
- type AuthorizeDeps
- type BatchPromptRequest
- type BatchRequest
- type BlockedEntry
- type BlockedPaths
- func (b *BlockedPaths) BlockedWithin(target string) (BlockedEntry, bool)
- func (b *BlockedPaths) Blocking(target string) (BlockedEntry, bool)
- func (b *BlockedPaths) Current() *BlockedPaths
- func (b *BlockedPaths) Degraded() bool
- func (b *BlockedPaths) Entries() []BlockedEntry
- func (b *BlockedPaths) FoldCase() bool
- func (b *BlockedPaths) Forms() []string
- func (b *BlockedPaths) Home() string
- func (b *BlockedPaths) InRepository(root string) ([]RepositoryEntry, error)
- func (b *BlockedPaths) OutsideProject(path string) bool
- func (b *BlockedPaths) ProjectRoot() string
- func (b *BlockedPaths) SetNotice(notice func(string))
- func (b *BlockedPaths) SetOnDegradedChange(changed func(degraded bool))
- type BlockedReload
- type Decision
- func Authorize(ctx context.Context, req MatchRequest, modeCtx ModeContext, deps AuthorizeDeps) (Decision, error)
- func AuthorizeBatch(ctx context.Context, reqs []BatchRequest, deps AuthorizeDeps) ([]Decision, error)
- func AuthorizeIndependent(ctx context.Context, reqs []BatchRequest, deps AuthorizeDeps) ([]Decision, error)
- type DeclaredCommand
- type DedupKey
- type DirectIdentity
- type DirectScopeChoice
- type DirectScopeKind
- type DirectScopeModel
- type GrantTable
- type HomeExpansion
- type LayerKind
- type LiteralReason
- type MatchRequest
- type MatchResult
- type ModeContext
- type MovePair
- type OperationClass
- type PermissionEntry
- type PromptChoice
- type PromptOutcome
- type PromptSummaryInput
- type PromptSummaryResult
- type RecordAppender
- type RepositoryEntry
- type ScopeEscalation
- type ShellToken
- type SimpleCompound
- type SplitResult
- type StageContext
- type StageNormalization
- type StageRoute
- type TurnCache
Constants ¶
const ( CapabilityWrite = "write" CapabilityWriteTree = "write_tree" CapabilityRead = "read*" )
Reserved capability subjects. A capability subject is valid only as the subject of a kind:tool grant; ParseEntry rejects it for any other kind. Approving a capability grant authorizes every concrete member tool at the grant's path scope and descendants, without a separate prompt per member tool.
const OutcomeSourceConfined = "confined"
const OutcomeSourceDeclaredCommand = "declared_command"
OutcomeSourceDeclaredCommand is the permission_matched outcome_source a project's declared command-list hit records, valid only on a non-host path record with outcome once and no grant fields.
const OutcomeSourceDeclaredScope = "declared_scope"
OutcomeSourceDeclaredScope is the permission_matched outcome_source a run's declared file scopes record, for both the approval and the terminal denial.
const PromptFactsOverflowNotice = "… additional authorization facts; inspect the complete request"
Variables ¶
This section is empty.
Functions ¶
func AuditSubjectMatches ¶
func AuditSubjectMatches(kind, requestSubject string, requestTokens, requestAssignments []string, requestClearEnv bool, storedSubject string) bool
For direct requests, requestTokens must be the persisted canonical tokens; re-splitting requestSubject loses quoted-token boundaries. requestAssignments and requestClearEnv must likewise come from a recorded request identity — an audit that cannot prove that state must not call this at all, because an empty list and a false flag assert the request carried no assignment and inherited the environment.
func BoundPromptFact ¶
BoundPromptFact renders one variable authorization fact for a fixed prompt block: every ASCII control byte becomes a visible \xHH escape kept whole, and the result is cut on a rune and escape boundary at 120 code points with an ellipsis.
func BuildDenyEnvelope ¶
BuildDenyEnvelope produces the compact JSON deny envelope for a user denial. The reason is the user's text with leading/trailing ASCII whitespace trimmed and capped to 512 UTF-8 bytes; an empty result after trimming yields the default reason. denied_by is always the literal "user".
func BuildGuardDenyEnvelope ¶
func BuildGuardDenyEnvelope() string
func BuildModeDenyEnvelope ¶
BuildModeDenyEnvelope produces the deny envelope for a mode-posture denial: the same pinned wire shape with denied_by "mode" and reason naming the active mode and restriction. reason is always supplied by the caller (modePosture), so the default-reason fallback never applies here; it is shared with the user path only to reuse the trim/cap normalization.
func CacheIdentity ¶
func CacheIdentity(req MatchRequest) any
CacheIdentity returns the comparable key under which the turn cache records req's outcome.
func CanonicalHost ¶
func CanonicalLiteralForm ¶
CanonicalLiteralForm returns the canonical literal-command subject for input. Leading and trailing whitespace is stripped, the command is tokenized with the shared shell parser (the same tokenizer direct-command normalization uses), and the tokens are re-emitted separated by a single ASCII space (U+0020).
Each token is normalized conservatively so that commands which execute identically collapse to one subject. A token whose unescaped value is free of any character the tokenizer cannot prove inert is re-emitted from that value in one canonical spelling — bare when the value needs no quoting, single-quoted otherwise — so quoting-style-equivalent spellings (`'x'`, `"x"`, bare `x`, `a\ b`) collapse to one form. A token whose value carries expansion (`$`, “ ` “), globbing (`* ? [ ] { }`), or shell-operator/structure characters (`< > & | ; ( )` and friends) is preserved as its verbatim original span (ShellToken.Raw), because single-quoting it could change what the command executes; this keeps `'$x'`, `"$x"`, and `$x` distinct and leaves redirection/operator forms intact. A token shaped like a shell assignment word (a valid name followed by `=`) is likewise preserved verbatim, so an environment assignment (`FOO=bar`) never collapses onto a quoted command word (`"FOO=bar"`) the shell would instead try to execute as a program. A token whose value is a shell reserved word (`for`, `if`, `in`, …) is likewise preserved verbatim, so the unquoted grammar form (`for`, which the shell parses as a loop keyword) never collapses onto the quoted command-word spelling (`"for"`, an ordinary word naming a program) the shell parses differently. A token whose raw span carries a backslash-newline line continuation is also preserved, because the shell removes that sequence (so `echo\<newline>hi` runs as `echohi`) while the tokenizer models it as a literal newline — re-quoting would collapse it onto a different command. Re-emitting each token in a single fixed spelling separated by one delimiter is idempotent — CanonicalLiteralForm(CanonicalLiteralForm(x)) equals CanonicalLiteralForm(x).
Normalization never widens what a grant authorizes: the program, flags, operators, and token values are unchanged — only the textual quoting representation of provably-equivalent tokens is normalized. The canonical form is what callers store as the subject, hash for the audit-trail argument_hash, and match against. A malformed command (unterminated quote or dangling backslash) returns the tokenizer's error so callers never silently broaden a permission match.
func CapabilityForMember ¶
func CapabilityMembers ¶
CapabilityMembers returns a copy of the concrete tool subjects the given capability subject expands to, and whether subject is a reserved capability. The copy keeps the underlying source-of-truth table immutable to callers.
func ClassifyDirectTokens ¶
ClassifyDirectTokens applies the flag/positional rules to the canonical argument tokens that follow the program token. The anchor index is -1 when no safe anchor exists.
A token is a flag when its first byte is '-' — this covers long flags (`--verbose`), `--key=value` forms, bundled short flags (`-abc`), and a bare '-'. The literal '--' token terminates flag parsing and is itself not a positional, so every token after it is positional regardless of a leading '-'. A path-looking token after a flag is skipped; a path-looking token elsewhere suppresses the inferred anchor.
func ClassifyExceptional ¶
func ClassifyExceptional(req MatchRequest, mc ModeContext) (authority string, classifiable bool)
func CleanAbsResolve ¶
CleanAbsResolve normalizes path into a cleaned, absolute, symlink-resolved directory path. It expands a leading ~ against home, resolves a relative path against baseDir, follows symlinks via filepath.EvalSymlinks, and requires the resolved target to be an existing directory. EvalSymlinks requires the path to exist, so a missing target is an error here — correct for a folder-grant anchor, which must name a real directory.
func ComputeArgumentHash ¶
ComputeArgumentHash returns the lowercase hex SHA-256 (64 chars) of the canonical literal subject, per the argument-hash algorithm.
func DecodeDirectRequestSubject ¶
func DecodeDirectRequestSubject(requestSubject, commandSubject string) (assignments []string, clearEnvironment, ok bool)
DecodeDirectRequestSubject validates a recorded direct request identity against its exact command identity and reports the request's ordered assignments and whether it cleared the environment. ok is false unless the pair proves that state, so an assignment-free inherited environment is never inferred from command-only text.
func DeliverDeny ¶
func DeliverDeny(toolCallID string, envelope string, appender RecordAppender)
DeliverDeny appends the deny envelope to the session record stream as a normal tool-result message: a role "tool" record with a fresh UUIDv7 id, the canonical millisecond UTC timestamp, string content equal to envelope, and tool_call_id referencing the denied assistant tool call. It does not flush; durability stays with the store's owner.
func DerivePathScope ¶
DerivePathScope resolves the path scope of a request being approved, by this precedence: a non-empty cdHint (the cd <path> && prefix) wins, otherwise a structured-tool subject anchors on toolCwd, and otherwise the codemeld process CWD is used. The selected raw path is resolved; relative cd hints and a relative toolCwd resolve against processCWD. The result is always a concrete directory and never the all-paths sentinel "*", which is reserved for stored global grants.
func DirectRequestSubject ¶
func DirectRequestSubject(commandTokens, assignments []string, clearEnvironment bool) (string, error)
DirectRequestSubject returns the request identity for one direct stage. Command tokens are serialized one shell word each and the assignments and clear-environment state are carried inside the versioned form, so no two distinct requests share an identity.
func DirectUnderReadable ¶
DirectUnderReadable is display only; the stored value is the versioned envelope and this spelling is never parsed back.
func EncodeCompoundSubject ¶
EncodeCompoundSubject assembles the deterministic, versioned generalized stored subject from a wrapper flag, the per-stage scope subjects the chooser produced (each an Exact / Subcommand / Program direct-subject string such as `gofmt -w a.go` or `go build :*`), and the operators between them. Every stage subject is re-rendered to one canonical spelling so equal generalizations encode byte-identically (and therefore hash and dedup identically). It fails closed — returning an error and no subject — on a malformed shape, an operator that is not simple-compound syntax, a stage that does not parse to a program, or a stage whose program IsDestructiveProgram flags: a destructive program is ineligible for widening, so the whole compound must stay byte-exact and no generalized grant is produced for it.
func EncodeDirectEnvSubject ¶
func EncodeDirectEnvSubject(assignments []string, clearEnvironment bool, subject string) (string, error)
EncodeDirectEnvSubject wraps a canonical direct scope subject and the request's ordered assignments into the versioned stored form. subject is the chooser-produced scope subject, so a bare trailing `:*` wildcard is preserved rather than quoted. clearEnvironment selects the disjoint v2 form, so a cleared stage can never be stored as its otherwise identical inherited-environment form.
func EncodeDirectUnderSubject ¶
func EncodeDirectUnderSubject(tokens []string, pathIndex int, boundary string, tailWildcard bool) (string, error)
EncodeDirectUnderSubject builds the stored containment subject from a request's canonical tokens. Tokens after pathIndex are dropped, so they may vary only when tailWildcard says so.
func ExactSubject ¶
ExactSubject returns the Exact stored subject: the full canonical token sequence (program followed by its argument tokens) with no `:*` suffix. Each value is serialized as exactly one shell word, so an empty or whitespace-bearing token survives re-tokenization instead of collapsing and letting a later token be wildcarded.
func ExceptionalAuthority ¶
func ExpandBlockedEntry ¶
func IsCapabilitySubject ¶
func IsDeeperScope ¶
func IsDescendantOrEqual ¶
IsDescendantOrEqual reports whether path equals scope or lies strictly within it. A grant authorizes its scope and every descendant, never an ancestor or a sibling. Both arguments are expected to be cleaned absolute paths; the relative-path computation makes the check boundary-safe so that scope "/a/foo" does not match the sibling "/a/foobar".
func IsDestructiveProgram ¶
IsDestructiveProgram reports whether granting a direct-command Program scope (`<program> :*`) for the given program should get the stronger destructive-breadth warning. It is advisory only: pure and side-effect-free, with no filesystem lookups, no argument or subcommand parsing, and no grant blocking — it informs warning copy and decides nothing.
The argument must be a canonical direct-command program token (the bare program name produced by direct-command normalization, e.g. PromptRequest.CanonicalProgram). It is matched exactly, so a path-like or otherwise non-canonical token such as `/tmp/rm` does not match by basename: the predicate derives no basename and only program tokens already canonicalized upstream can match.
func IsModeDenyEnvelope ¶
IsModeDenyEnvelope reports whether envelope is a well-formed deny envelope carrying a recoverable denied_by that the turn loop re-issues, as opposed to a terminal user refusal. It shares the closed recoverability rule with IsRecoverableDeniedBy and fails closed: a malformed, reasonless, or unrecognized envelope returns false, so an unclassifiable denial is still treated as terminal.
func IsRecoverableDeniedBy ¶
IsRecoverableDeniedBy reports whether a decoded denied_by names a policy denial the turn loop re-issues instead of halting on.
func LiteralRewriteAsks ¶
func LiteralRewriteAsks(stage StageNormalization, runDir string) bool
func LiteralRunsHg ¶
LiteralRunsHg and LiteralRunsPS report whether a literal-route command runs a named program in any stage, after the `/dev/null` redirects classification strips. They fail closed on text they cannot decompose, which only costs a route that never reached the program.
func LiteralRunsPS ¶
func MarshalEntry ¶
func MarshalEntry(e PermissionEntry) ([]byte, error)
MarshalEntry serializes one entry to YAML for persistence callers, emitting keys in on-disk order and guaranteeing path_scope == "*" is a quoted scalar rather than a bare "*" (which YAML would read as an alias).
func MatchesDeclaredCommand ¶
func MatchesDeclaredCommand(commandTokens, assignments []string, clearEnvironment bool, entries []DeclaredCommand) bool
A declaration is a prefix of the canonical direct token sequence and spells no environment, so it authorizes only the invocation that inherits codemeld's own environment: it is matched through the same rule a stored unversioned grant subject uses, under which an assignment-bearing or environment-clearing request is a distinct request that never matches. The environment is not cosmetic — dispatch applies it to the child process, an assigned or cleared PATH re-resolves the program name against that environment, and a program's own configuration variables load code the declared tokens never named. classifyReadOnlyShell refuses the read-only auto path on the same ground.
func MatchesDirectSubject ¶
func MatchesDirectSubject(commandTokens, assignments []string, clearEnvironment bool, storedSubject string) bool
MatchesDirectSubject reports whether a direct request — its canonical command token sequence plus the ordered environment assignments stripped from it and whether it clears the environment — matches the stored direct-command grant subject. A versioned subject authorizes only its exact ordered assignments and clear state and applies its decoded scope subject to the command tokens; every unversioned subject keeps its legacy assignment-free, environment-inheriting reading, so neither an assignment-bearing nor a clearing request ever matches one.
func MatchesLiteralSubject ¶
MatchesLiteralSubject reports whether a canonical literal command subject matches a stored literal grant subject: a literal subject matches only itself, and two literal subjects are equal only when their canonical forms are byte-equal. Both arguments must already be canonical — callers pass the output of CanonicalLiteralForm — because matching performs no normalization itself. Whitespace- and quoting-style-equivalent commands match because CanonicalLiteralForm already collapsed them to one form upstream, not because this function re-normalizes; two forms that are not byte-equal after canonicalization never match here.
func ParseWritableFolders ¶
func PrefixSubject ¶
func ProbeStageReadOnly ¶
func ProgramSubject ¶
ProgramSubject returns the Program stored subject: the shell-quoted program token, a space, then `:*` (e.g. `make :*`), authorizing any invocation of the program within the scope. A whitespace-bearing program is quoted first, so the wildcard stays its own token.
func ReadOnlyShell ¶
func ReadOnlyShell(req MatchRequest, mc ModeContext) bool
func ShellPathAsks ¶
func ShellPathAsks(b *BlockedPaths, folder string, argv, candidates []string) bool
func ShellPathCandidates ¶
A relative candidate is joined without cleaning: `link/../x` names a different file than `x` when `link` is a symlink, and the blocked-path matcher compares both the cleaned and resolved forms.
func ShellQuote ¶
ShellQuote renders value as one shell word in canonical form: returned bare when every character is safe unquoted, otherwise single-quoted with any embedded single quote spliced as the standard '\” sequence. value is assumed free of expansion/glob/operator characters (see containsShellMeta), so single-quoting reproduces it verbatim with no shell interpretation.
func VisibleControlEscape ¶
VisibleControlEscape renders untrusted request text for a terminal that has no scrollback discipline of its own: every ASCII control byte other than a line feed or tab becomes a visible \xHH escape, so a request cannot conceal, clear, or reposition what the prompt prints after it. Nothing is dropped or shortened, so the complete request stays inspectable.
Types ¶
type ApprovalCache ¶
type ApprovalCache interface {
Lookup(req MatchRequest) (outcome string, hit bool)
Record(req MatchRequest, outcome string)
}
type AuthorizeDeps ¶
type AuthorizeDeps struct {
Grants GrantTable
Cache ApprovalCache
Prompt func(ctx context.Context, esc ScopeEscalation) (PromptOutcome, error)
PromptBatch func(ctx context.Context, reqs []BatchPromptRequest) (PromptOutcome, error)
// PromptIndependent asks the user about a group of independently authorizable requests and
// returns an outcome per prompted request, index-aligned with reqs. It may ask one question for
// several requests that are the same decision and fan that answer out to each of them, so the
// number of screens shown is its own concern; this package only requires the per-request
// outcomes, which it applies independently. Only AuthorizeIndependent uses it.
PromptIndependent func(ctx context.Context, reqs []BatchPromptRequest) ([]PromptOutcome, error)
PersistGrant func(entry PermissionEntry) (PermissionEntry, error)
// PersistGrantAt binds persistence to one request of an independent group by its index in the
// AuthorizeIndependent input, so a failed persist degrades to that request's own path scope
// rather than a sibling's. AuthorizeIndependent falls back to PersistGrant when it is nil.
PersistGrantAt func(index int) func(entry PermissionEntry) (PermissionEntry, error)
// EmitPromptShown receives the request's canonical direct tokens verbatim;
// callers must never rebuild them from the flattened subject.
EmitPromptShown func(subjectKind, subject, pathScope string, directTokens []string, id DirectIdentity) error
EmitDecision func(subjectKind, subject, pathScope, outcome, grantKind, grantPathScope string, id DirectIdentity) error
EmitDecisionGrant func(subjectKind, subject, pathScope, grantKind, grantPathScope, grantSubject string, id DirectIdentity) error
EmitMatched func(subjectKind, subject, pathScope, grantKind, grantPathScope, outcome, outcomeSource, argumentHash, directRequestSubject string) error
}
Avoid import cycles with internal/config and internal/tui.
type BatchPromptRequest ¶
type BatchPromptRequest struct {
Req MatchRequest
Escalation ScopeEscalation
// Index is the stage's position in the AuthorizeBatch input.
Index int
}
type BatchRequest ¶
type BatchRequest struct {
Req MatchRequest
ModeCtx ModeContext
}
type BlockedEntry ¶
func (BlockedEntry) Floor ¶
func (e BlockedEntry) Floor() bool
type BlockedPaths ¶
type BlockedPaths struct {
// contains filtered or unexported fields
}
func NewBlockedPaths ¶
func NewBlockedPaths(home string, entries []BlockedEntry) *BlockedPaths
func NewLiveBlockedPaths ¶
func NewLiveBlockedPaths(home, projectRoot string, entries []BlockedEntry, reload func() BlockedReload) *BlockedPaths
func (*BlockedPaths) BlockedWithin ¶
func (b *BlockedPaths) BlockedWithin(target string) (BlockedEntry, bool)
The floor is checked last so a configured entry, whose reason names it, is reported when both lie inside target.
func (*BlockedPaths) Blocking ¶
func (b *BlockedPaths) Blocking(target string) (BlockedEntry, bool)
func (*BlockedPaths) Current ¶
func (b *BlockedPaths) Current() *BlockedPaths
func (*BlockedPaths) Degraded ¶
func (b *BlockedPaths) Degraded() bool
func (*BlockedPaths) Entries ¶
func (b *BlockedPaths) Entries() []BlockedEntry
func (*BlockedPaths) FoldCase ¶
func (b *BlockedPaths) FoldCase() bool
func (*BlockedPaths) Forms ¶
func (b *BlockedPaths) Forms() []string
func (*BlockedPaths) Home ¶
func (b *BlockedPaths) Home() string
func (*BlockedPaths) InRepository ¶
func (b *BlockedPaths) InRepository(root string) ([]RepositoryEntry, error)
func (*BlockedPaths) OutsideProject ¶
func (b *BlockedPaths) OutsideProject(path string) bool
func (*BlockedPaths) ProjectRoot ¶
func (b *BlockedPaths) ProjectRoot() string
func (*BlockedPaths) SetNotice ¶
func (b *BlockedPaths) SetNotice(notice func(string))
func (*BlockedPaths) SetOnDegradedChange ¶
func (b *BlockedPaths) SetOnDegradedChange(changed func(degraded bool))
Holding the refresh lock makes removal wait out an in-flight refresh, so the old callback never runs after this returns.
type BlockedReload ¶
type BlockedReload struct {
Changed bool
Entries []BlockedEntry
Problems map[string]string
}
type Decision ¶
type Decision struct {
Outcome string
Grant *PermissionEntry
DenyEnvelope string
// DenyReason and DenialSource describe a denial this package decided without a prompt,
// so a caller can classify it without reading the envelope text. Both stay empty for an
// allow and for a denial the prompt outcome supplied.
DenyReason string
DenialSource string
// ReadOnlyAuto marks the one allow the active mode granted because the request classified
// read-only. A prompt, a grant, a cache hit, a declared-scope allow, and every other mode
// posture leave it false.
ReadOnlyAuto bool
PersistErr error
// WrapUp is never set by Authorize: a caller's authorizer sets it on a refusal after which the
// turn must end with one tool-less answer round.
WrapUp bool
}
func Authorize ¶
func Authorize(ctx context.Context, req MatchRequest, modeCtx ModeContext, deps AuthorizeDeps) (Decision, error)
func AuthorizeBatch ¶
func AuthorizeBatch(ctx context.Context, reqs []BatchRequest, deps AuthorizeDeps) ([]Decision, error)
func AuthorizeIndependent ¶
func AuthorizeIndependent(ctx context.Context, reqs []BatchRequest, deps AuthorizeDeps) ([]Decision, error)
AuthorizeIndependent authorizes a group of requests that are independently authorizable: each resolves on its own, so a denial never stops a sibling, and the requests that still need a decision are gathered into one prompt returning an outcome per request. Each request keeps its own subject, decision, grant, persistence scope, and deny envelope. A prompt error — a cancellation or a transport failure — resolves nothing, so no caller may run any member. AuthorizeBatch stays the direct-pipeline path, where one outcome covers every stage and a deny stops the pipeline.
type DeclaredCommand ¶
type DeclaredCommand struct {
Subject string
}
func ParseDeclaredCommands ¶
func ParseDeclaredCommands(raw any) (entries []DeclaredCommand, notices []string, err error)
type DirectIdentity ¶
DirectIdentity carries a direct request's boundary-preserving identities to the event log, which stores them opaquely; only this package decodes them. Both are empty for every non-direct request.
type DirectScopeChoice ¶
type DirectScopeChoice struct {
Kind DirectScopeKind
Subject string
Pinned []string
Readable string
Boundaries []string
Boundary int
TailWildcard bool
}
DirectScopeChoice is one selectable stored subject for a direct-command grant. Pinned holds the argument tokens the subject stores verbatim (all of them for Exact, the wildcard's prefix for Prefix, none for Program), so a surface can render what a choice fixes without re-deriving it.
A DirectScopePath choice carries an opaque versioned envelope in Subject, so a surface renders Readable instead. Boundaries runs from the path argument's immediate parent outward and Boundary indexes the one this choice stores.
func (DirectScopeChoice) RequiresConfirmation ¶
func (c DirectScopeChoice) RequiresConfirmation() bool
type DirectScopeKind ¶
type DirectScopeKind int
const ( DirectScopeExact DirectScopeKind = iota DirectScopePrefix DirectScopeProgram DirectScopePath )
type DirectScopeModel ¶
type DirectScopeModel struct {
Program string
Args []string
Choices []DirectScopeChoice
Recommended int
// contains filtered or unexported fields
}
DirectScopeModel is the direct-command grant-scope policy. It must stay presentation-neutral: no labels, warnings, or input handling, so every prompt surface renders its own copy over one policy.
func NewDirectScopeModel ¶
func NewDirectScopeModel(tokens []string, home string) (DirectScopeModel, bool)
NewDirectScopeModel derives the scope ladder from a request's canonical tokens: Exact, then one Prefix choice per visible token boundary from the longest pinned prefix to the shortest, then Program. The zero-length prefix is deliberately absent — that subject is the Program choice, which is the only one requiring a confirmation.
home is the bootstrap-resolved home directory, canonicalized here and used only as the containment floor; callers must not resolve it themselves.
func (DirectScopeModel) PathChoiceAt ¶
func (m DirectScopeModel) PathChoiceAt(boundary int) (DirectScopeChoice, bool)
func (DirectScopeModel) PathRow ¶
func (m DirectScopeModel) PathRow() int
PathRow is the index of the containment choice in Choices, or -1 when none is offered.
type GrantTable ¶
type GrantTable interface {
Permissions() []PermissionEntry
AppendPermission(PermissionEntry)
}
type HomeExpansion ¶
type HomeExpansion struct {
// Expand is true only when the token began with a bare (unquoted, unescaped)
// leading `~` or `$HOME` followed by `/` or end-of-token — the sole forms
// this slice expands. `~user` and general `$VAR` substitution stay false.
Expand bool
// Suffix is the token text after the stripped `~` / `$HOME` prefix (e.g.
// "/srht/codemeld/que" for `~/srht/codemeld/que`, "" for a bare `~`). The
// runner argv token is the resolved home directory followed by Suffix.
Suffix string
}
HomeExpansion is the execution-only home-directory expansion for one direct token, carried parallel to StageNormalization.Tokens so runner argv can expand a bare leading `~` or `$HOME` while the canonical Tokens used for authorization subjects stay literal and unexpanded. It is deliberately kept off Tokens so a stored grant never holds the resolved home path.
type LayerKind ¶
type LayerKind string
LayerKind names the settings layer an entry was read from. It is in-memory provenance: no settings file may carry it, and it takes no part in authorization or deduplication.
type LiteralReason ¶
type LiteralReason string
LiteralReason names why a stage was routed to literal matching. It is informational (for events and prompts); routing is the same for every value.
const ( // ReasonWrapperCommand: the leading program is a wrapper/indirect // launcher (sh, bash, env, command, nice, nohup, time, xargs). ReasonWrapperCommand LiteralReason = "wrapper-command" // ReasonBackgrounding: the stage contains an unquoted backgrounding `&`. ReasonBackgrounding LiteralReason = "backgrounding" // ReasonSubshell: the stage contains an unquoted subshell `(...)`. ReasonSubshell LiteralReason = "subshell" // ReasonProcessSubstitution: the stage contains an unquoted process // substitution `<(...)` or `>(...)`. ReasonProcessSubstitution LiteralReason = "process-substitution" )
type MatchRequest ¶
type MatchRequest struct {
Kind string
Subject string
SubjectTokens []string
// Assignments is part of what a grant authorizes, so an empty list is the
// request "this command carries no assignment", never "unknown".
Assignments []string
// ClearEnvironment is likewise part of what a grant authorizes: false is the
// request "this command inherits the environment", never "unknown".
ClearEnvironment bool
PathScope string
Host string
FreshConfirmation bool
}
MatchRequest describes one permission check. Subject is the canonical subject string used by literal and tool matching, cache keys, and events; for a direct request it is the boundary-preserving request identity DirectRequestSubject builds, not the flattened command text. SubjectTokens is the canonical command token sequence and is required only when Kind == subjectKindDirect; literal and tool matching use Subject and ignore SubjectTokens. PathScope is the request's resolved, cleaned, absolute directory and is never the all-paths sentinel "*". Host is an already canonical hostname for host requests.
type MatchResult ¶
type MatchResult struct {
Matched bool
Grant *PermissionEntry
OutcomeSource string
}
MatchResult is the outcome of Match. On a real-grant match, Matched is true, Grant points at a copy of the winning entry, and OutcomeSource is outcomeSourceGrant. On no match, Matched is false, Grant is nil, and OutcomeSource is empty; Match never consults the turn cache, so a no-match result carries no cache outcome source — reading the cache after a no-match is owned by the turn cache and prompt orchestration.
func Match ¶
func Match(req MatchRequest, grants []PermissionEntry) MatchResult
type ModeContext ¶
type ModeContext struct {
Mode string
Class OperationClass
ToolName string
Stages []StageNormalization
LiteralRoute bool
TargetPath string
MoveSource string
MoveDestination string
ProjectRoot string
// DeclaredWrite and DeclaredDelete are the run's canonical declared file scopes. A
// non-empty pair activates the ceiling, so an empty list under an active ceiling denies
// its operation class rather than deferring it to ordinary authorization. Under ChildAgent a nil
// DeclaredDelete or DeclaredMoves is an omitted declaration and a non-nil empty one was supplied
// empty. No codemeld run flag sets DeclaredMoves.
DeclaredWrite map[string]bool
DeclaredDelete map[string]bool
DeclaredMoves map[MovePair]bool
// ChildAgent marks the declared scopes as a child agent's delegate declaration rather than a
// codemeld run flag pair.
ChildAgent bool
// ChildInvocationID identifies which child agent's calls these are, for the attribution the
// caller's event emitters record. Nothing here reads it; authorization is unchanged by it.
ChildInvocationID string
// DeclaredCommands is the project's declared command list: Auto posture only, and
// never consulted in Plan or Build.
DeclaredCommands []DeclaredCommand
// DelegateWrite, DelegateDelete, and DelegateMoves are resolved before the grandchild starts, so the
// subset checks against the declared forms are an authorization decision rather than a report on a
// running child.
DelegateWrite map[string]bool
DelegateDelete map[string]bool
DelegateMoves map[MovePair]bool
OpenRead bool
ExternalRead bool
ShellPathAsk bool
NetworkEgress bool
// ExecutionConstrained is the launch self-test result, not the displayed capability label.
ExecutionConstrained bool
SettingsBroken bool
WritableFolders []string
}
type OperationClass ¶
type OperationClass string
const ( ClassReadOnly OperationClass = "read_only" ClassMutating OperationClass = "mutating" )
type PermissionEntry ¶
type PermissionEntry struct {
Kind string
Subject string
PathScope string
HostScope string
GrantKind string
ArgumentHash string
Layer LayerKind
}
PermissionEntry is the in-memory form of one on-disk permission entry. PathScope is "*" for a global grant or a cleaned absolute path for a folder grant. ArgumentHash is set only when Kind == "literal". Layer is stamped by the loader that read the entry, never by the entry's own bytes.
func DegradeGrant ¶
func DegradeGrant(entry PermissionEntry, requestPath string) PermissionEntry
DegradeGrant returns the entry to keep active in memory when persisting a grant fails: the grant stays usable for the run but can never be re-persisted. requestPath is the resolved, cleaned absolute request directory (never "*").
func ParseEntry ¶
ParseEntry validates one decoded YAML mapping from the permissions list and returns the typed entry. index is the entry's position in the permissions array; it and filePath are woven into every error so load-time callers can surface -compatible failures (offending file, entry index, and the path_scope/grant_kind field pair) without re-parsing the entry.
func (PermissionEntry) DedupKey ¶
func (e PermissionEntry) DedupKey() DedupKey
DedupKey returns the comparable identity used to merge or dedup entries.
type PromptChoice ¶
type PromptChoice int
Avoid an import cycle: internal/tui imports internal/permissions.
const ( PromptAllowOnce PromptChoice = iota PromptDeny PromptAllowSession PromptAllowFolder PromptAllowGlobal PromptAllowProject )
type PromptOutcome ¶
type PromptOutcome struct {
Choice PromptChoice
StoredSubject string
DenyEnvelope string
}
type PromptSummaryInput ¶
type PromptSummaryInput struct {
Action string
Command string
RequestedTarget string
Body string
HostRequest string
Stages []string
}
PromptSummaryInput is the display-only payload one prompt renders, carried beside the MatchRequest so the summary never renders matching data.
type PromptSummaryResult ¶
type PromptSummaryResult struct {
Kind string
Sentence string
Effect string
Targets []string
Unknown bool
StageCount int
Lines int
Chars int
}
PromptSummaryResult is display data only: no matching subject, grant input, or path a caller could act on.
func PromptSummary ¶
func PromptSummary(req MatchRequest, in PromptSummaryInput) PromptSummaryResult
PromptSummary explains one authorization request for the prompt. It fails closed: any request whose effect and target it cannot establish from the closed shell grammar below or from a prepared tool's lexical arguments reports the unknown-effect fallback rather than a guess.
func (PromptSummaryResult) DisplayRows ¶
func (s PromptSummaryResult) DisplayRows() []string
DisplayRows is the ordered fixed block both prompt surfaces render above the decision choices. The first two rows are the summary sentence and the request-shape line; a renderer with a row budget keeps those two and may drop the target rows that follow.
type RecordAppender ¶
RecordAppender is the minimal seam BuildDenyEnvelope's delivery path needs: *sessions.Store satisfies it via Append(sessions.Record), and tests pass a stub so they stay independent of session-file flushing.
type RepositoryEntry ¶
type RepositoryEntry struct {
Entry BlockedEntry
Candidates []string
}
type ScopeEscalation ¶
type ScopeEscalation struct {
NarrowerScope string
}
ScopeEscalation is a display-only signal computed on the prompt path. When NarrowerScope is non-empty, the request's path scope is strictly broader than (an ancestor of) a folder the user already approved for this same subject, and NarrowerScope names that narrowest already-approved folder. The prompt uses it to warn that the request broadens an existing grant; it changes nothing about matching, caching, or persistence. The zero value (empty NarrowerScope) means the request is not a scope escalation.
func FindScopeEscalation ¶
func FindScopeEscalation(req MatchRequest, grants []PermissionEntry) ScopeEscalation
FindScopeEscalation reports whether req's path scope is a strict ancestor of an already-approved, same-subject grant — i.e. the request is broader than, and contains, a folder the user already allowed for the same kind and subject. It scans grants for a non-global grant whose kind and subject match req by the same rules Match uses (so a stored direct ":*" program scope or a tool capability still matches, not just a raw-string-equal subject), and whose path scope is a strict descendant of req's path scope. NarrowerScope is the narrowest (deepest) such already-approved scope, or empty when req broadens no same-subject grant.
It is display-only: the caller passes the result to the prompt purely to make the broadening salient — it reads grants but changes no matching, cache, or persistence. Global grants are skipped: a global grant would have matched req outright (so the prompt is never reached), and "*" is not a narrower folder.
type ShellToken ¶
type ShellToken struct {
// Value is the unescaped, unquoted content — the string a shell would pass
// to the program as a single argv entry. Literal canonicalization normalizes
// most tokens from this value (see CanonicalLiteralForm).
Value string
// Raw is the verbatim input span that produced this token, including the
// original quote characters and backslashes. Literal canonicalization falls
// back to Raw for tokens whose value carries expansion/glob/operator syntax
// it cannot prove equivalent.
Raw string
// Quoted is true when any character of the token appeared inside single or
// double quotes. Direct-command normalization uses it to keep a quoted `:*`
// from being read as the wildcard token.
Quoted bool
// contains filtered or unexported fields
}
ShellToken is one whitespace-delimited word parsed from a command stage by tokenizeShell. It carries the parsed value plus enough original-span metadata for both direct-command and literal-command normalization to decide how to re-emit the token.
type SimpleCompound ¶
SimpleCompound is the ordered decomposition of a recognized simple compound command: its inner direct-command stages with their pinned operator structure. Wrapped reports the single accepted wrapper flavour, `sh -c '<compound>'`, as opposed to a bare simple compound; the two never cross-match. Stages holds each stage's canonical direct-command token sequence (program name followed by its arguments), and Operators holds the len(Stages)-1 operators between them, each `&&`, `;`, `||`, or `|`. It is the shape the per-stage scope chooser reads to build options, and the shape the generalized matcher compares a request against.
func RecognizeSimpleCompound ¶
func RecognizeSimpleCompound(canonical string) (SimpleCompound, bool)
RecognizeSimpleCompound decomposes a canonical literal command into its ordered direct-command stages, or reports notSimple=false when the command is anything but a simple compound. The accepted shapes are the single wrapper form `sh -c '<stage> [&& <stage>]…'` and bare simple compounds/pipelines whose every stage is otherwise a direct command. Every other construct — a subshell, process substitution, backgrounding, a redirection, `$(...)`, a backtick, a nested wrapper, an env-assignment-bearing stage, an `sh -c` form with extra positional arguments, or any unparseable stage — returns false so the caller keeps today's byte-exact literal behaviour. It is conservative by construction: when in doubt it returns false, because a false "not simple" is only friction while a false "simple" would widen a grant.
canonical must already be the CanonicalLiteralForm output. The matcher decomposes the request command with this function and compares it against a decoded grant; the prompt uses it to drive the per-stage scope chooser.
func RecognizeSimpleCompoundForGrant ¶
func RecognizeSimpleCompoundForGrant(canonical string) (SimpleCompound, bool)
RecognizeSimpleCompoundForGrant decides grant identity only: it ignores bare `/dev/null` redirects, so a caller deciding which executor runs the command must keep using SimpleCompoundStages, which still refuses every redirect.
type SplitResult ¶
type SplitResult struct {
CdHint string
Stages []StageNormalization
LiteralRoute bool
LiteralInput string
AllowedShell bool
RejectedConstruct string
}
SplitResult is the outcome of normalizing a whole shell command per the strip-then-split-then-normalize order. Exactly one of three routes applies and the caller selects on the boolean fields:
AllowedShell == false: a rejected construct was found.
RejectedConstruct names it; the permission system is skipped entirely (no prompt, no grant authorizes). Stages is nil and LiteralRoute is false. - AllowedShell == true && LiteralRoute == true: the command is syntactically accepted but outside the direct-command boundary (I/O redirection, a wrapper/literal-route stage, a bare simple compound, or a double-quoted command substitution). The whole prefix-stripped command is matched as a single literal subject — LiteralInput carries it (feed to CanonicalLiteralForm). Stages is nil. - AllowedShell == true && LiteralRoute == false: every pipeline stage is a direct command. Stages holds one RouteDirect StageNormalization per stage,
each authorized independently.
CdHint carries the raw `<path>` from a leading `cd <path> && ` prefix, unquoted exactly as the shell would pass it to cd, or "" when no such prefix is present. It is preserved on the direct and literal routes so DerivePathScope can anchor the path scope; ~ expansion, absolute conversion, and symlink resolution are DerivePathScope's job, not this function's.
func SplitCommand ¶
func SplitCommand(input string) (SplitResult, error)
SplitCommand normalizes a whole shell command into a SplitResult per the step order: (1) strip a leading `cd <path> && ` prefix once, capturing <path> as CdHint; (2) on the remainder, refuse rejected constructs, then route I/O redirection, wrapper/literal forms, and bare simple compounds (`&&`, `;`, `||`) to a single literal subject, or (3) split on unquoted `|` and normalize each stage through NormalizeDirectStage.
The returned error is reserved for malformed input that cannot be classified at all — an unterminated quote, a dangling backslash, an empty command, or an empty pipeline stage. A command that is well-formed but uses a construct outside the allowlist is NOT an error: it returns AllowedShell == false with RejectedConstruct set, because routes that case to the agent rather than to Go error handling. Operator detection is quote-aware: `|`, `;`, `&&`, `||`, newline/carriage-return separators, redirection, `$(`, and backticks inside single/double quotes or escaped are literal text, never shell syntax, except that a `$(` or backtick inside double quotes routes the command literal.
type StageContext ¶
type StageContext int
StageContext exists for the flagless script-invocation grammar alone (scriptInvocationStage), which is admitted only for a standalone command so a script pipeline stays one literal subject executing through the outer `sh -c`. StageInCompound is the zero value so a caller that omits the context fails closed.
const ( StageInCompound StageContext = iota StageSolo )
type StageNormalization ¶
type StageNormalization struct {
Route StageRoute
Tokens []string
// ExecForms is the per-token home-directory expansion for execution, index-
// aligned with Tokens. It carries the minimal source-form detail dispatch
// needs to expand a bare leading `~` / `$HOME` in runner argv only, without
// changing the canonical Tokens used for matching. nil on a RouteLiteral
// stage; on a RouteDirect stage every entry is present (Expand false for a
// token that is not a bare leading HOME form, including the program token).
ExecForms []HomeExpansion
// Assignments holds the leading KEY=VALUE environment assignments stripped
// from a RouteDirect stage (step 3), in original order, each as the
// shell-resolved `KEY=VALUE` string (name and separator unquoted, value
// already unescaped — `FOO="a b"` yields `FOO=a b`). They are deliberately
// kept out of Tokens so direct-subject matching authorizes on the program and
// its arguments alone, but preserved here so an authorized stage executes with
// the environment the agent's command requested rather than the parent
// environment only. nil when the stage had no leading assignments or routes to
// literal matching.
Assignments []string
// ClearEnvironment is true for an accepted `env -i` stage: the child receives Assignments alone
// rather than the inherited environment. It is also part of the stage's authorization identity,
// so a cleared stage never matches, caches, or persists as its otherwise identical inheriting
// form.
ClearEnvironment bool
// LookupPath is the $PATH resolution captured for an accepted `command -v <name>` or
// `type <name>` stage (commandLookupStage, typeLookupStage). Both are shell builtins with no
// executable to launch, so dispatch answers such a stage from this value instead of building runner
// argv; resolving it a second time at execution would authorize one lookup and perform another.
// Empty on every other stage.
LookupPath string
// ProgramUnescapedSeparator is true when the program word's source spelling has a `/` that was
// bare or quoted rather than backslash-escaped.
ProgramUnescapedSeparator bool
// Source is prompt-only stage text.
Source string
Reason LiteralReason
}
StageNormalization is the result of normalizing one pipeline stage. When Route is RouteDirect, Tokens holds the canonical token sequence (bare program name followed by its arguments in original order) and Reason is empty. When Route is RouteLiteral, Tokens is nil and Reason explains the routing.
func LiteralStages ¶
func LiteralStages(canonical string) []StageNormalization
func NormalizeDirectStage ¶
func NormalizeDirectStage(stage string, ctx StageContext) (StageNormalization, error)
NormalizeDirectStage normalizes one already-isolated pipeline stage: it strips leading KEY=VALUE environment assignments, rewrites the leading executable to its bare program name, and returns the canonical token sequence for direct-command matching. Wrapper and indirect forms are tagged for literal routing rather than returned as direct tokens.
Preconditions owned by the caller: the pipeline has already been split on `|`, any `cd <path> && ` prefix has been stripped, rejected constructs (`;`, `&&` outside the allowed form, `||`, `$(...)`, backticks) have already been refused, and I/O redirection has already been routed to literal matching. This function does not re-detect those.
func SimpleCompoundStages ¶
func SimpleCompoundStages(command string) (wrapped bool, stages []StageNormalization, operators []string, ok bool)
SimpleCompoundStages accepts canonical input for matching and source input for prompt display.
type StageRoute ¶
type StageRoute int
StageRoute classifies how a normalized pipeline stage must be matched.
const ( // RouteDirect means the stage is a plain exec-style command whose canonical // token sequence (StageNormalization.Tokens) is matched against // direct-command grants. RouteDirect StageRoute = iota // RouteLiteral means the stage is a wrapper or indirect form that // must not be treated as a direct command; the caller routes it to // literal-command matching instead. No direct tokens are produced. RouteLiteral )
type TurnCache ¶
type TurnCache struct {
// contains filtered or unexported fields
}
TurnCache is the process-local, in-memory turn-scoped approval cache. It memoizes the resolved outcome of every permission prompt for the duration of a single model turn so an agent's retry-on-transient-failure pattern does not re-prompt the user within that turn, while every distinct user-driven turn still re-confirms intent. It holds no grants: a hit short-circuits with the cached outcome only and never creates or modifies a session/folder/global PermissionEntry. Real grants live in the matcher's table and always win over the cache, so callers consult it only when Match returns no match. The zero value is ready to use. Not safe for concurrent use; the turn loop drives it from a single goroutine.
func NewTurnCache ¶
func NewTurnCache() *TurnCache
NewTurnCache returns an empty TurnCache ready for the first turn.
func (*TurnCache) Lookup ¶
func (c *TurnCache) Lookup(req MatchRequest) (outcome string, hit bool)
Lookup returns the cached outcome for req and whether one was recorded this turn (read rule). A hit reads no grant — the caller short-circuits with outcome and shows no prompt. Consult Lookup only after the real-grant Match returns no match, since a real grant always wins over the cache.
func (*TurnCache) Record ¶
func (c *TurnCache) Record(req MatchRequest, outcome string)
Record stores req's resolved outcome for the rest of the turn (write rule). Every prompt resolution is recorded, including deny, so a denied retry of the same subject within the turn short-circuits instead of re-prompting. outcome is opaque here — one of once/deny/session_grant/folder_grant/ global_grant per §Reserved Values — and is not validated. Recording creates or modifies no grant.
func (*TurnCache) Reset ¶
func (c *TurnCache) Reset()
Reset empties the cache so the next turn starts with no cached decisions (clear rule). The caller invokes it at the start of every new user submission, before any of the new turn's permission checks run; the boundary itself is owned by prompt/runtime orchestration, while TurnCache only provides the hook.
Source Files
¶
- awkprogram.go
- blockedpaths.go
- capability.go
- compound.go
- declared.go
- degrade.go
- denyenvelope.go
- destructive_program.go
- direct_scope.go
- entry.go
- exceptional.go
- matcher.go
- normalize_direct.go
- normalize_literal.go
- orchestrate.go
- pipeline.go
- planshell.go
- promptsummary.go
- scope.go
- shell_tokens.go
- shellpaths.go
- turncache.go
- writable_folders.go