From 72b2098030759c0254fbc91e6bb86be317eb354a Mon Sep 17 00:00:00 2001 From: weichen Date: Sat, 10 Oct 2026 02:06:12 +0000 Subject: [PATCH] Let in-proc plugins register the EXT-8 resource authorizer (fix rdm.* unknown-command) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- internal/app/compiled.go | 13 +++++++ internal/app/compiled_authz_test.go | 55 +++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 internal/app/compiled_authz_test.go diff --git a/internal/app/compiled.go b/internal/app/compiled.go index 2b39059..c961697 100644 --- a/internal/app/compiled.go +++ b/internal/app/compiled.go @@ -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 diff --git a/internal/app/compiled_authz_test.go b/internal/app/compiled_authz_test.go new file mode 100644 index 0000000..f081f8b --- /dev/null +++ b/internal/app/compiled_authz_test.go @@ -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 应被拒绝") + } +}