skillaccess

package
v0.60.0 Latest Latest
Warning

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

Go to latest
Published: Jul 19, 2026 License: AGPL-3.0 Imports: 6 Imported by: 0

Documentation

Overview

Package skillaccess owns the direct authorization rules for DB-backed Skill resources. Every skill-row read, write, delete, and scope-management decision flows through this domain, under a trusted authz.Authority that no request/model input can forge:

  • HTTP transports call the Begin/Authorize* methods with the caller's Authority;
  • the agent skills tool (reads) and the reflect reviewer tool (reads and its prompt-driven create/patch/deprecate) reach the decisions through the consumer-owned skills.SkillReadAuthorizer / SkillWriteAuthorizer ports (BeginRead / BeginWrite), which reconstruct the turn/worker identity from context — a user or delegated agent, or a confined GroupAgentActor for a group turn;
  • reflect's staged skill reconciliation and usage curation authorize each write through AuthorizeWorkerWrite under a fresh WorkerAgentAuthority.

Filesystem project/system skills merged read-only by internal/skills are not DB rows and are not security-sensitive; they are outside this PEP's scope.

Index

Constants

View Source
const (
	ScopeUser        = "user"
	ScopeUserAgent   = "user_agent"
	ScopeSystem      = "system"
	ScopeSystemAgent = "system_agent"
)

The four durable skill scopes (the DB scope column). system and system_agent are admin-managed; user and user_agent are owned by their user.

Variables

View Source
var (
	ErrForbidden    = errors.New("skill access forbidden")
	ErrNotFound     = errors.New("skill not found")
	ErrUnavailable  = errors.New("skill authorization unavailable")
	ErrInvalidScope = errors.New("invalid skill scope")
)

Errors returned by the Skill access rules. Denials on user-owned scopes are opaque (ErrNotFound, 404) so a foreign skill cannot be told from a missing one; admin-managed system scopes surface ErrForbidden (403).

Functions

This section is empty.

Types

type Access

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

Access captures one validated authority for a Skill use case. Skill owns the direct rules for DB-backed skill rows; every decision reads only immutable authority plus the durable row/scope facts, so there is no revision to re-read between use cases.

func (*Access) AuthorizeAgent

func (a *Access) AuthorizeAgent(ctx context.Context, agentID string) error

AuthorizeAgent folds the route agent's read gate into this use case. The agent-scoped skill endpoints reach a skill through /api/agents/{id}/skills, so the path agent {id} is gated alongside the skill decision, for every scope — including user and system DB skills whose own row is not agent-bound. This replaces the preliminary requireAgentAccess split evaluation.

func (*Access) AuthorizeList

func (a *Access) AuthorizeList() error

AuthorizeList authorizes the collection-level skill list. Per-scope and per-row visibility is decided separately in the same use case (AuthorizeManageScope / AuthorizeManage).

func (*Access) AuthorizeManage

func (a *Access) AuthorizeManage(ctx context.Context, sk skills.Skill, action authz.Action) error

AuthorizeManage authorizes an action on one already-loaded skill row. Reading, writing, or deleting a user/user_agent skill requires the acting user to own it (else the denial is opaque, ErrNotFound); any action on an admin-managed system/system_agent skill requires the admin superuser (ErrForbidden). For agent-bound scopes the caller's read access to the skill's agent is folded in.

func (*Access) AuthorizeManageByID

func (a *Access) AuthorizeManageByID(ctx context.Context, id string, action authz.Action) (skills.Skill, error)

AuthorizeManageByID loads a skill row by id and authorizes an action on it. A missing row is opaque (ErrNotFound). It returns the loaded row so the caller can perform the store mutation it just authorized.

func (*Access) AuthorizeManageScope

func (a *Access) AuthorizeManageScope(ctx context.Context, scope, agentID string) (uid, aid string, err error)

AuthorizeManageScope authorizes managing a whole scope bucket — create, install, upload, or the scope-keyed management list — and returns the owner columns a row of that scope carries. user/user_agent require the acting user to be the owner (always true here); system/system_agent require the admin superuser. When agentID is non-empty the caller's read access to that agent is folded in (the former requireAgentAccess gate).

The management-list reuses this create-authority decision deliberately: the pre-cutover resolveSkillManageScope was the single gate for both listing and creating a scope, so denying a scope also hides its list.

func (*Access) AuthorizeRead

func (a *Access) AuthorizeRead(ctx context.Context, sk skills.Skill) error

AuthorizeRead authorizes reading one DB-backed skill row through the resolve/ list surface (agent skills tool, agent HTTP list/get/file). Unlike AuthorizeManage it uses ActionRead without escalating system scopes to Manage, so a user or delegated agent may read shared system/system_agent procedures while a since-revoked agent grant still hides the row. For agent-bound scopes the caller's read access to the skill's agent is folded in. A user-scope denial is opaque (ErrNotFound); a system-scope denial is ErrForbidden — the caller decides whether to skip (list) or 404 (single load).

type Service

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

Service is the composition-root-owned Skill authorization domain. It holds the narrow skill read port and the Agent-domain gate (to fold an agent-read check into a skill decision).

func NewService

func NewService(store SkillStore, agents *agentaccess.Service) *Service

NewService constructs the Skill authorization domain. agents is the Agent-domain gate; store is the read port used to load a row for a by-id decision.

func (*Service) AuthorizeWorkerWrite

func (s *Service) AuthorizeWorkerWrite(ctx context.Context, userID, agentID, skillID string, create bool) error

AuthorizeWorkerWrite authorizes a durable worker's write to a reflect-owned user_agent skill under a freshly reconstructed WorkerAgentAuthority(userID, agentID). create=true authorizes minting a new row (scope create); otherwise it authorizes writing the existing skillID. It is the single reauthorization point for reflect's staged skill reconciliation and usage curation, so a since-revoked agent grant stops the write.

func (*Service) Begin

func (s *Service) Begin(_ context.Context, authority authz.Authority) (*Access, error)

Begin captures validated authority for one Skill use case.

func (*Service) BeginRead

func (s *Service) BeginRead(ctx context.Context) (skills.SkillReadDecision, error)

BeginRead implements skills.SkillReadAuthorizer for the skills tool. It derives the trusted turn/worker identity from the runtime context (an agent turn or the reflect reviewer's WithUserID+WithAgentID review context, both of which ToAuthority resolves to an AgentActor — the same shape as a reconstructed WorkerAgentAuthority) and returns a per-row read decider. A missing/invalid identity fails closed. Writes never flow through this path.

func (*Service) BeginWrite

func (s *Service) BeginWrite(ctx context.Context) (skills.SkillWriteDecision, error)

BeginWrite implements skills.SkillWriteAuthorizer for the reflect reviewer tool. It reconstructs the trusted turn identity from context (the reflect review target's WithUserID+WithAgentID → an AgentActor) and authorizes each create/patch/deprecate before the store mutation.

type SkillStore

type SkillStore interface {
	ListAll(ctx context.Context) ([]skills.Skill, error)
}

SkillStore is the narrow read port the PEP needs to resolve one skill row by id. The skills store has no direct Get-by-id yet, so the PEP scans ListAll — the same approach the transports used, correct at current volumes.

Jump to

Keyboard shortcuts

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