update the archive options
Some checks failed
CI / test (push) Failing after 4m40s
CI / fixture (push) Failing after 7s

This commit is contained in:
2026-09-10 19:30:46 +05:30
parent 74089eb3e9
commit 4e1f746b22
5 changed files with 388 additions and 0 deletions

View File

@@ -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 {