update the archive options
This commit is contained in:
@@ -34,6 +34,10 @@ type DefinitionsService struct {
|
||||
// unknownTools reports which of a spec's tool names are not registered.
|
||||
// nil means no check — the shipped importer path, which has its own.
|
||||
unknownTools func([]string) []string
|
||||
|
||||
// curatedAgents holds the definition ids the deployment ships specs for.
|
||||
// nil or empty means nothing is protected — see WithCuratedAgents.
|
||||
curatedAgents map[string]bool
|
||||
}
|
||||
|
||||
// NewDefinitions builds a definitions service over a repository.
|
||||
@@ -58,6 +62,38 @@ func (s *DefinitionsService) WithToolCheck(unknown func([]string) []string) *Def
|
||||
return s
|
||||
}
|
||||
|
||||
// WithCuratedAgents teaches the service which agents ship with the product.
|
||||
//
|
||||
// A curated agent's ORGANIZATION row is the product's own definition of it,
|
||||
// published by `importagents` from the specs in the deployment image. Deleting
|
||||
// that row does not remove a shipped agent from the interface — the frontend
|
||||
// still bundles it — it removes the definition the RUNTIME resolves from, so
|
||||
// the agent keeps appearing in the list and every run of it answers 404. That
|
||||
// is a worse outcome than a refused request, and it is reachable today by any
|
||||
// operator with a terminal: the list never offers the button, but the endpoint
|
||||
// has never enforced what the button implies.
|
||||
//
|
||||
// PERSONAL rows are deliberately NOT protected, even when they carry a curated
|
||||
// id. A personal definition of a shipped id is an override somebody authored,
|
||||
// and deleting it is exactly the "Revert to shipped" action the Agents list
|
||||
// offers. Protecting by id alone would break that.
|
||||
//
|
||||
// Injected rather than read here so this package does not learn where the
|
||||
// specs live, and so a test can name its own protected set — the same shape as
|
||||
// WithToolCheck above.
|
||||
func (s *DefinitionsService) WithCuratedAgents(ids map[string]bool) *DefinitionsService {
|
||||
s.curatedAgents = ids
|
||||
return s
|
||||
}
|
||||
|
||||
// isCurated reports whether an agent definition is one the product ships.
|
||||
func (s *DefinitionsService) isCurated(definitionID string) bool {
|
||||
if s.curatedAgents == nil {
|
||||
return false
|
||||
}
|
||||
return s.curatedAgents[definitionID]
|
||||
}
|
||||
|
||||
// rejectUnknownTools fails a definition that names a tool that does not exist.
|
||||
func (s *DefinitionsService) rejectUnknownTools(markdown string) error {
|
||||
if s.unknownTools == nil {
|
||||
@@ -355,6 +391,18 @@ func (s *DefinitionsService) DeleteAgent(ctx context.Context, ident authctx.Iden
|
||||
if !known || role == domain.RoleTalent {
|
||||
return nil, domain.Forbidden()
|
||||
}
|
||||
|
||||
// A curated agent is the product's own, and is refused here rather than
|
||||
// merely hidden in the list. Only the organization tier is protected: a
|
||||
// personal row carrying the same id is somebody's override, and removing
|
||||
// it is the supported way back to the shipped definition.
|
||||
//
|
||||
// Placed after the role check so the answer to a talent user is the same
|
||||
// 403 for every organization row, curated or not — which agents a tenant
|
||||
// runs is not something an unprivileged caller should be able to probe.
|
||||
if did, _ := existing["definition_id"].(string); s.isCurated(did) {
|
||||
return nil, domain.Forbidden()
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := s.repo.DeleteAgent(ctx, ident, id); err != nil {
|
||||
|
||||
Reference in New Issue
Block a user