diff --git a/src/gateway/method-scopes.test.ts b/src/gateway/method-scopes.test.ts index e60c79c6d03..d2180273de4 100644 --- a/src/gateway/method-scopes.test.ts +++ b/src/gateway/method-scopes.test.ts @@ -389,6 +389,36 @@ describe("core gateway method classification", () => { ); expect(unclassified).toStrictEqual([]); }); + + it("exposes skill proposal methods through the core gateway registry", () => { + for (const method of ["skills.proposals.list", "skills.proposals.inspect"]) { + expect(listGatewayMethods()).toContain(method); + expect(coreGatewayHandlers).toHaveProperty(method); + expect(resolveLeastPrivilegeOperatorScopesForMethod(method)).toEqual(["operator.read"]); + expect(authorizeOperatorScopesForMethod(method, ["operator.read"])).toEqual({ + allowed: true, + }); + } + + for (const method of [ + "skills.proposals.create", + "skills.proposals.update", + "skills.proposals.apply", + "skills.proposals.reject", + "skills.proposals.quarantine", + ]) { + expect(listGatewayMethods()).toContain(method); + expect(coreGatewayHandlers).toHaveProperty(method); + expect(resolveLeastPrivilegeOperatorScopesForMethod(method)).toEqual(["operator.admin"]); + expect(authorizeOperatorScopesForMethod(method, ["operator.write"])).toEqual({ + allowed: false, + missingScope: "operator.admin", + }); + expect(authorizeOperatorScopesForMethod(method, ["operator.admin"])).toEqual({ + allowed: true, + }); + } + }); }); describe("CLI default operator scopes", () => { diff --git a/src/gateway/methods/core-descriptors.ts b/src/gateway/methods/core-descriptors.ts index ef2408e5c4d..a6aa2fec288 100644 --- a/src/gateway/methods/core-descriptors.ts +++ b/src/gateway/methods/core-descriptors.ts @@ -117,6 +117,13 @@ export const CORE_GATEWAY_METHOD_SPECS: readonly CoreGatewayMethodSpec[] = [ { name: "skills.upload.commit", scope: "operator.admin" }, { name: "skills.install", scope: "operator.admin" }, { name: "skills.update", scope: "operator.admin" }, + { name: "skills.proposals.list", scope: "operator.read" }, + { name: "skills.proposals.inspect", scope: "operator.read" }, + { name: "skills.proposals.create", scope: "operator.admin" }, + { name: "skills.proposals.update", scope: "operator.admin" }, + { name: "skills.proposals.apply", scope: "operator.admin" }, + { name: "skills.proposals.reject", scope: "operator.admin" }, + { name: "skills.proposals.quarantine", scope: "operator.admin" }, { name: "update.status", scope: "operator.admin" }, { name: "update.run", scope: "operator.admin", controlPlaneWrite: true }, { name: "voicewake.get", scope: "operator.read" }, diff --git a/src/gateway/server-methods.ts b/src/gateway/server-methods.ts index 798660e6876..cce3e13c38d 100644 --- a/src/gateway/server-methods.ts +++ b/src/gateway/server-methods.ts @@ -433,6 +433,13 @@ export const coreGatewayHandlers: GatewayRequestHandlers = { "skills.skillCard", "skills.install", "skills.update", + "skills.proposals.list", + "skills.proposals.inspect", + "skills.proposals.create", + "skills.proposals.update", + "skills.proposals.apply", + "skills.proposals.reject", + "skills.proposals.quarantine", ], loadHandlers: loadSkillsHandlers, }),