feat: namespace plugin permissions and keep them valid when a plugin is off

This commit is contained in:
LukeGus committed 2026-09-22 17:30:00 -05:00
1 parent 8431570ff7
commit cc15559ea1
53 files changed
+31442 -351

No files matched your search

@@ -14,12 +14,12 @@
},
"capabilities": ["kv:own", "ui:surface"],
"contributes": {
"permissionGroup": {
"group": "sample-plugin",
"permissions": ["sample-plugin.use"],
"defaultForRole": {
"user": ["admin.users.manage"]
"permissions": [
{
"name": "admin.users.manage",
"titleKey": "permissions.admin.users.manage.title",
"descriptionKey": "permissions.admin.users.manage.description"
}
}
]
}
}
@@ -14,10 +14,13 @@
},
"capabilities": ["kv:own", "ui:surface"],
"contributes": {
"permissionGroup": {
"group": "sample-plugin",
"permissions": ["sample-plugin.use"]
},
"permissions": [
{
"name": "use",
"titleKey": "permissions.use.title",
"descriptionKey": "permissions.use.description"
}
],
"actions": [
{
"id": "sample.open",
@@ -21,9 +21,12 @@
}
],
"contributes": {
"permissionGroup": {
"group": "sample-plugin",
"permissions": ["sample-plugin.use"]
}
"permissions": [
{
"name": "use",
"titleKey": "permissions.use.title",
"descriptionKey": "permissions.use.description"
}
]
}
}
+3 -3
View File
@@ -79,9 +79,9 @@ describe("validate-plugin-manifest.cjs", () => {
expect(output).toContain("extra");
});
// A manifest must not be able to hand a role a permission the plugin does
// not own, which is how a plugin could otherwise grant itself admin rights.
it("rejects a role default outside the plugin's own namespace", () => {
// A manifest must not be able to name a core namespace, which is how a
// plugin could otherwise gate a route on admin authority it never had.
it("rejects a permission name outside the plugin's own namespace", () => {
const { status, output } = runValidator(
"invalid-role-default-escalation.json",
);
+4 -6
View File
@@ -135,12 +135,10 @@ check("host really deleted", await hosts.findById(host.id), null);
// A plugin's own tables. The DDL is emitted from a table definition rather
// than written by hand, so this is what proves the emitter's output is valid
// on a real engine - the unit tests can only snapshot the string.
const { defineTable, id, varchar, text, refUser, timestamp } = await import(
"../packages/plugin-sdk/dist/db.js"
);
const { createTableSql, dropTableSql } = await import(
"../packages/plugin-sdk/dist/ddl.js"
);
const { defineTable, id, varchar, text, refUser, timestamp } =
await import("../packages/plugin-sdk/dist/db.js");
const { createTableSql, dropTableSql } =
await import("../packages/plugin-sdk/dist/ddl.js");
const { sql: raw } = await import("drizzle-orm");
const pluginTable = defineTable(