Let in-proc plugins register the EXT-8 resource authorizer (fix rdm.* unknown-command)
ci / vet-and-test (pull_request) Successful in 42s

Why: pluginRegistry never overrode RegisterResourceAuthorizer, so
in-proc extensions hit the declarative-branch rejection; register()
aborted wholesale and every plugin command vanished from the bus. The
RDM binary ships the EXT-8 member adapter since step-4, meaning the
served RDM process answers 'unknown or disabled command' for every
rdm.* call — caught by the MS1 re-acceptance smoke replay (fb/report/
confirm/lifecycle smokes all failed on rdm.baseline).

Evidence: TestInprocPluginRegistersAuthorizerAndCommands — red on the
parent commit (registration aborts, dispatch rejected, nil-slot denies),
green with the fix (command committed; the shared authz slot answers
alice-allow/bob-deny through projectAuthz). Full go test ./... green;
RDM MS0 acceptance re-run follows on the pin bump.

Impact: pluginRegistry.RegisterResourceAuthorizer fills the shared
hostcaps.authz slot (first registrant wins, mirroring the composition
root); compiled/declarative branch unchanged.

Refs: RDM G1-8 MS1 re-acceptance; EXT-8 (d89e748 seam); RDM #113.
This commit is contained in:
weichen
2026-10-10 02:06:12 +00:00
parent e03d3ac6c6
commit 72b2098030
2 changed files with 68 additions and 0 deletions
+13
View File
@@ -86,6 +86,19 @@ func (r compiledRegistry) RegisterResourceAuthorizer(extension.ResourceAuthorize
return errors.New("extension: declarative plugins cannot register resource authorizers")
}
// RegisterResourceAuthorizer 进程内扩展(EXT-1):填充 EXT-8 适配器槽
// (hostcaps.authz 共享指针;首个注册者生效,组合根已装配进 Server/runmgr)。
// 此前 pluginRegistry 未覆盖该方法——进程内插件注册适配器会命中声明式分支的
// 拒绝,register() 整体中止,插件命令全部未注册(RDM 二进制 rdm.* 全部
// 「unknown command」;MS1 重验收烟测实测发现)。
func (r pluginRegistry) RegisterResourceAuthorizer(fn extension.ResourceAuthorizer) error {
if fn == nil {
return errors.New("extension: " + r.pluginID + ": nil resource authorizer")
}
r.caps.authz.set(r.pluginID, fn)
return nil
}
func (r compiledRegistry) Note(kind, name string, payload json.RawMessage) error {
log.Printf("extension: note %s %s (%d bytes)", kind, name, len(payload))
return nil
+55
View File
@@ -0,0 +1,55 @@
package app
import (
"context"
"testing"
"time"
"agent-system-backend/internal/cmdbus"
"agent-system-backend/internal/store"
"agent-system-backend/pkg/extension"
core "agent-system-core"
)
// fakeExtAuthz 进程内扩展:注册 EXT-8 适配器+一条命令——修复前适配器注册被
// 声明式分支拒绝,register() 中止导致命令全部丢失。
type fakeExtAuthz struct{}
func (f *fakeExtAuthz) Register(reg extension.Registry) error {
if err := reg.RegisterResourceAuthorizer(func(owner string, projectID core.ID) (bool, error) {
return owner == "alice", nil
}); err != nil {
return err
}
return reg.AddCommandHandler([]byte(`{"id":"t.authz.ping","title":"t"}`),
func(ctx context.Context, h extension.Host, req extension.Request) (any, error) {
return map[string]any{"ok": true}, nil
})
}
func TestInprocPluginRegistersAuthorizerAndCommands(t *testing.T) {
extension.ResetCompiled()
extension.RegisterCompiled(extension.InProc{ID: "t", Register: (&fakeExtAuthz{}).Register})
defer extension.ResetCompiled()
st, err := store.OpenSQLite(t.TempDir() + "/t.db")
if err != nil {
t.Fatal(err)
}
defer st.Close()
bus := cmdbus.New(st, nil, func() time.Time { return time.Unix(1700000000, 0).UTC() })
caps := hostcaps{st: st, authz: &projectAuthz{}}
applyCompiled("", bus, caps)
// ① 命令已注册且可分发(修复前:适配器注册被拒 → register() 中止 →
// 命令全部丢失,rdm.* 全部 unknown command)。
if resp := bus.Dispatch(context.Background(), cmdbus.Request{
ID: "t.authz.ping", Entrance: "button", Owner: "alice"}); resp.Receipt.State != "committed" {
t.Fatalf("命令分发 = %+v(注册整体中止)", resp.Receipt)
}
// ② 适配器经共享槽生效:alice 放行、bob 拒绝。
if ok, _ := caps.authz.Authorize("alice", "p1"); !ok {
t.Fatal("alice 应放行")
}
if ok, _ := caps.authz.Authorize("bob", "p1"); ok {
t.Fatal("bob 应被拒绝")
}
}