feat(iot): IoT 插件化(Provider/Manager/mock 8设备/能力约束/bus 事件);feat(capability/restrict): 工具级布尔标记(群聊/陌生人可用,默认关,策略引擎雏形)+ registry ToolPolicy
This commit is contained in:
1 parent
915aa8a100
commit
b3c61aa1ff
9 files changed
+1358
No files matched your search
@@ -34,6 +34,18 @@ const (
|
||||
KindIoT Kind = "iot"
|
||||
)
|
||||
|
||||
// ToolPolicy holds the editable Boolean markers that gate a tool/capability in
|
||||
// non-owner environments (DevDocs/design/11 §3). Both fields default to false,
|
||||
// so a capability is owner-only until a marker is explicitly enabled. Policies
|
||||
// are per-capability and editable at runtime (the admin panel, M10).
|
||||
type ToolPolicy struct {
|
||||
// AllowInGroup permits invocation from a group chat environment.
|
||||
AllowInGroup bool
|
||||
// AllowInStranger permits invocation from a stranger's private chat
|
||||
// environment.
|
||||
AllowInStranger bool
|
||||
}
|
||||
|
||||
// Capability is a globally shared, always-available capability registered in
|
||||
// the registry.
|
||||
type Capability struct {
|
||||
@@ -43,6 +55,9 @@ type Capability struct {
|
||||
Kind Kind
|
||||
// Description is a human-readable summary used for selection and display.
|
||||
Description string
|
||||
// Policy is the editable reachability markers for this capability. It
|
||||
// defaults to zero, which the policy engine reads as owner-only.
|
||||
Policy ToolPolicy
|
||||
// Value is the underlying capability object. For KindTool/KindSkill/
|
||||
// KindChannel it is the SDK interface from pkg/plugins/sdk; for KindMCP and
|
||||
// KindIoT it is owned by the relevant subsystem.
|
||||
@@ -103,6 +118,18 @@ func (r *Registry) Get(name string) (Capability, error) {
|
||||
return cap, nil
|
||||
}
|
||||
|
||||
// Remove deletes the capability registered under name. It returns ErrNotFound
|
||||
// when no such capability is registered.
|
||||
func (r *Registry) Remove(name string) error {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
if _, ok := r.caps[name]; !ok {
|
||||
return fmt.Errorf("registry: remove %q: %w", name, ErrNotFound)
|
||||
}
|
||||
delete(r.caps, name)
|
||||
return nil
|
||||
}
|
||||
|
||||
// List returns a copy of the capabilities whose Kind matches kind. An empty
|
||||
// kind returns every registered capability. The returned slice is owned by the
|
||||
// caller and safe to mutate.
|
||||
@@ -119,6 +146,60 @@ func (r *Registry) List(kind Kind) []Capability {
|
||||
return out
|
||||
}
|
||||
|
||||
// Policy returns the editable reachability markers for the capability named
|
||||
// name. It returns ErrNotFound when no such capability is registered.
|
||||
func (r *Registry) Policy(name string) (ToolPolicy, error) {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
cap, ok := r.caps[name]
|
||||
if !ok {
|
||||
return ToolPolicy{}, fmt.Errorf("registry: policy %q: %w", name, ErrNotFound)
|
||||
}
|
||||
return cap.Policy, nil
|
||||
}
|
||||
|
||||
// SetPolicy replaces the reachability markers for the capability named name. It
|
||||
// returns ErrNotFound when no such capability is registered.
|
||||
func (r *Registry) SetPolicy(name string, p ToolPolicy) error {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
cap, ok := r.caps[name]
|
||||
if !ok {
|
||||
return fmt.Errorf("registry: set policy %q: %w", name, ErrNotFound)
|
||||
}
|
||||
cap.Policy = p
|
||||
r.caps[name] = cap
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetAllowInGroup toggles the group-chat marker for the capability named name.
|
||||
// It returns ErrNotFound when no such capability is registered.
|
||||
func (r *Registry) SetAllowInGroup(name string, allow bool) error {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
cap, ok := r.caps[name]
|
||||
if !ok {
|
||||
return fmt.Errorf("registry: set allow_in_group %q: %w", name, ErrNotFound)
|
||||
}
|
||||
cap.Policy.AllowInGroup = allow
|
||||
r.caps[name] = cap
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetAllowInStranger toggles the stranger-chat marker for the capability named
|
||||
// name. It returns ErrNotFound when no such capability is registered.
|
||||
func (r *Registry) SetAllowInStranger(name string, allow bool) error {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
cap, ok := r.caps[name]
|
||||
if !ok {
|
||||
return fmt.Errorf("registry: set allow_in_stranger %q: %w", name, ErrNotFound)
|
||||
}
|
||||
cap.Policy.AllowInStranger = allow
|
||||
r.caps[name] = cap
|
||||
return nil
|
||||
}
|
||||
|
||||
// RegisterTool registers t as a KindTool capability.
|
||||
func (r *Registry) RegisterTool(t sdk.Tool) error {
|
||||
if t == nil {
|
||||
|
||||
@@ -246,3 +246,70 @@ func TestRegistry_ConcurrentSafety(t *testing.T) {
|
||||
func kn(w, i int) string {
|
||||
return "cap" + strconv.Itoa(w) + "-" + strconv.Itoa(i)
|
||||
}
|
||||
|
||||
func TestRegistry_ToolPolicyDefaultsFalse(t *testing.T) {
|
||||
t.Parallel()
|
||||
r := New()
|
||||
if err := r.RegisterTool(regTool{name: "calc"}); err != nil {
|
||||
t.Fatalf("RegisterTool: %v", err)
|
||||
}
|
||||
pol, err := r.Policy("calc")
|
||||
if err != nil {
|
||||
t.Fatalf("Policy: %v", err)
|
||||
}
|
||||
if pol.AllowInGroup || pol.AllowInStranger {
|
||||
t.Fatalf("default policy = %+v, want both markers false", pol)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegistry_SetPolicyAndSetters(t *testing.T) {
|
||||
t.Parallel()
|
||||
r := New()
|
||||
if err := r.RegisterTool(regTool{name: "calc"}); err != nil {
|
||||
t.Fatalf("RegisterTool: %v", err)
|
||||
}
|
||||
|
||||
if err := r.SetPolicy("calc", ToolPolicy{AllowInGroup: true, AllowInStranger: true}); err != nil {
|
||||
t.Fatalf("SetPolicy: %v", err)
|
||||
}
|
||||
pol, _ := r.Policy("calc")
|
||||
if !pol.AllowInGroup || !pol.AllowInStranger {
|
||||
t.Fatalf("after SetPolicy = %+v, want both true", pol)
|
||||
}
|
||||
|
||||
if err := r.SetAllowInGroup("calc", false); err != nil {
|
||||
t.Fatalf("SetAllowInGroup: %v", err)
|
||||
}
|
||||
pol, _ = r.Policy("calc")
|
||||
if pol.AllowInGroup {
|
||||
t.Fatalf("SetAllowInGroup(false) did not clear group marker: %+v", pol)
|
||||
}
|
||||
if !pol.AllowInStranger {
|
||||
t.Fatalf("SetAllowInGroup unexpectedly cleared stranger marker: %+v", pol)
|
||||
}
|
||||
|
||||
if err := r.SetAllowInStranger("calc", false); err != nil {
|
||||
t.Fatalf("SetAllowInStranger: %v", err)
|
||||
}
|
||||
pol, _ = r.Policy("calc")
|
||||
if pol.AllowInGroup || pol.AllowInStranger {
|
||||
t.Fatalf("after clearing both = %+v, want both false", pol)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegistry_Policy_NotFound(t *testing.T) {
|
||||
t.Parallel()
|
||||
r := New()
|
||||
if _, err := r.Policy("missing"); !errors.Is(err, ErrNotFound) {
|
||||
t.Fatalf("Policy(missing) error = %v, want ErrNotFound", err)
|
||||
}
|
||||
if err := r.SetPolicy("missing", ToolPolicy{}); !errors.Is(err, ErrNotFound) {
|
||||
t.Fatalf("SetPolicy(missing) error = %v, want ErrNotFound", err)
|
||||
}
|
||||
if err := r.SetAllowInGroup("missing", true); !errors.Is(err, ErrNotFound) {
|
||||
t.Fatalf("SetAllowInGroup(missing) error = %v, want ErrNotFound", err)
|
||||
}
|
||||
if err := r.SetAllowInStranger("missing", true); !errors.Is(err, ErrNotFound) {
|
||||
t.Fatalf("SetAllowInStranger(missing) error = %v, want ErrNotFound", err)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user