package authz import ( "agent-system-backend/internal/authz/catalog" "agent-system-backend/internal/store" ) type Descriptor = catalog.Descriptor type Scope = catalog.Scope const ( ScopeSystem = catalog.ScopeSystem ScopeProject = catalog.ScopeProject ) func Catalog() []Descriptor { return catalog.Catalog() } type Decision struct { Allowed bool `json:"allowed"` ActionID string `json:"actionId"` Source string `json:"source,omitempty"` Reason string `json:"reason,omitempty"` AuthorityEpoch int64 `json:"authorityEpoch"` } type Service struct { store *store.Store } func NewService(st *store.Store) *Service { return &Service{store: st} } func (s *Service) AuthorizeSystem(actor, actionID string) Decision { decision := Decision{ActionID: actionID} descriptor, ok := catalog.DescriptorByID(actionID) if !ok || descriptor.Scope != catalog.ScopeSystem || s == nil || s.store == nil { decision.Reason = "unknown-system-action" return decision } epoch, err := s.store.AuthorityEpoch() if err != nil { decision.Reason = "authority-unavailable" return decision } decision.AuthorityEpoch = epoch account, err := s.store.GetAccountByOwner(actor) if err != nil || !account.Enabled { decision.Reason = "account-unavailable" return decision } // Project access comes only from the actor's role (R2): neither is_admin // nor a system grant confers it. if actionID == catalog.ProjectAccessAction { roleAllowed, _, err := s.store.RolePermissionAllowed(actor, actionID) if err != nil { decision.Reason = "authority-unavailable" return decision } if roleAllowed { decision.Allowed, decision.Source = true, "role" return decision } decision.Reason = "not-granted" return decision } // is_admin remains the narrow bootstrap/root recovery source. Ordinary // delegated management uses explicit system_grants. if account.IsAdmin { decision.Allowed, decision.Source = true, "bootstrap-admin" return decision } allowed, err := s.store.SystemGrantAllowed(actor, actionID) if err != nil { decision.Reason = "authority-unavailable" return decision } if allowed { decision.Allowed, decision.Source = true, "system-grant" return decision } decision.Reason = "not-granted" return decision } func (s *Service) EffectiveSystemActions(actor string) []string { actions := []string{} for _, descriptor := range catalog.Catalog() { if descriptor.Scope != catalog.ScopeSystem { continue } if decision := s.AuthorizeSystem(actor, descriptor.ActionID); decision.Allowed { actions = append(actions, descriptor.ActionID) } } return actions }