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
- Variables
- type Access
- func (a *Access) AuthorizeAgent(ctx context.Context, agentID string) error
- func (a *Access) AuthorizeList() error
- func (a *Access) AuthorizeManage(ctx context.Context, sk skills.Skill, action authz.Action) error
- func (a *Access) AuthorizeManageByID(ctx context.Context, id string, action authz.Action) (skills.Skill, error)
- func (a *Access) AuthorizeManageScope(ctx context.Context, scope, agentID string) (uid, aid string, err error)
- func (a *Access) AuthorizeRead(ctx context.Context, sk skills.Skill) error
- type Service
- func (s *Service) AuthorizeWorkerWrite(ctx context.Context, userID, agentID, skillID string, create bool) error
- func (s *Service) Begin(_ context.Context, authority authz.Authority) (*Access, error)
- func (s *Service) BeginRead(ctx context.Context) (skills.SkillReadDecision, error)
- func (s *Service) BeginWrite(ctx context.Context) (skills.SkillWriteDecision, error)
- type SkillStore
Constants ¶
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 ¶
var ( ErrForbidden = errors.New("skill access forbidden") ErrNotFound = errors.New("skill not found") 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 ¶
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 ¶
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 ¶
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 ¶
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) BeginRead ¶
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 ¶
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 ¶
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.