@bevel-software/platform-core-backend 0.7.4 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -202
- package/THIRD-PARTY-NOTICES.md +428 -454
- package/dist/core/core-ports.d.ts +1 -1
- package/dist/core/create-core-server.d.ts.map +1 -1
- package/dist/core/create-core-server.js +51 -7
- package/dist/core/create-core-server.js.map +1 -1
- package/dist/core/create-core-services.d.ts +5 -3
- package/dist/core/create-core-services.d.ts.map +1 -1
- package/dist/core/create-core-services.js +38 -21
- package/dist/core/create-core-services.js.map +1 -1
- package/dist/modules/access/access-control.interface.d.ts +8 -7
- package/dist/modules/access/access-control.interface.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.d.ts +1 -1
- package/dist/modules/access/access-control.service.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.js +6 -6
- package/dist/modules/access/access-control.service.js.map +1 -1
- package/dist/modules/access/access-declarations.d.ts +5 -5
- package/dist/modules/access/access-declarations.js +3 -3
- package/dist/modules/access/access-mutation.service.d.ts +3 -3
- package/dist/modules/access/access-mutation.service.d.ts.map +1 -1
- package/dist/modules/access/access-mutation.service.js +3 -3
- package/dist/modules/access/access-mutation.service.js.map +1 -1
- package/dist/modules/access/access-splice.js +4 -4
- package/dist/modules/access/access-splice.js.map +1 -1
- package/dist/modules/access/access.routes.js +19 -19
- package/dist/modules/access/access.routes.js.map +1 -1
- package/dist/modules/access/creator-access.d.ts +2 -2
- package/dist/modules/access/creator-access.js +5 -5
- package/dist/modules/access/creator-access.js.map +1 -1
- package/dist/modules/access/roles-admin.service.d.ts +18 -4
- package/dist/modules/access/roles-admin.service.d.ts.map +1 -1
- package/dist/modules/access/roles-admin.service.js +14 -4
- package/dist/modules/access/roles-admin.service.js.map +1 -1
- package/dist/modules/code-mode/code-mode-names.d.ts +5 -13
- package/dist/modules/code-mode/code-mode-names.d.ts.map +1 -1
- package/dist/modules/code-mode/code-mode-names.js +5 -27
- package/dist/modules/code-mode/code-mode-names.js.map +1 -1
- package/dist/modules/code-mode/code-mode.tool.d.ts.map +1 -1
- package/dist/modules/code-mode/code-mode.tool.js +29 -7
- package/dist/modules/code-mode/code-mode.tool.js.map +1 -1
- package/dist/modules/mcp/mcp.service.d.ts +11 -52
- package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
- package/dist/modules/mcp/mcp.service.js +33 -395
- package/dist/modules/mcp/mcp.service.js.map +1 -1
- package/dist/modules/plugins/index.d.ts +7 -0
- package/dist/modules/plugins/index.d.ts.map +1 -0
- package/dist/modules/plugins/index.js +6 -0
- package/dist/modules/plugins/index.js.map +1 -0
- package/dist/modules/plugins/join-proposals.d.ts +53 -0
- package/dist/modules/plugins/join-proposals.d.ts.map +1 -0
- package/dist/modules/plugins/join-proposals.js +67 -0
- package/dist/modules/plugins/join-proposals.js.map +1 -0
- package/dist/modules/plugins/join-requests.service.d.ts +81 -0
- package/dist/modules/plugins/join-requests.service.d.ts.map +1 -0
- package/dist/modules/plugins/join-requests.service.js +135 -0
- package/dist/modules/plugins/join-requests.service.js.map +1 -0
- package/dist/modules/plugins/plugin-provision.service.d.ts +134 -0
- package/dist/modules/plugins/plugin-provision.service.d.ts.map +1 -0
- package/dist/modules/plugins/plugin-provision.service.js +344 -0
- package/dist/modules/plugins/plugin-provision.service.js.map +1 -0
- package/dist/modules/plugins/plugins.contract.d.ts +106 -0
- package/dist/modules/plugins/plugins.contract.d.ts.map +1 -0
- package/dist/modules/plugins/plugins.contract.js +36 -0
- package/dist/modules/plugins/plugins.contract.js.map +1 -0
- package/dist/modules/plugins/plugins.routes.d.ts +42 -0
- package/dist/modules/plugins/plugins.routes.d.ts.map +1 -0
- package/dist/modules/plugins/plugins.routes.js +379 -0
- package/dist/modules/plugins/plugins.routes.js.map +1 -0
- package/dist/modules/plugins/plugins.service.d.ts +60 -0
- package/dist/modules/plugins/plugins.service.d.ts.map +1 -0
- package/dist/modules/plugins/plugins.service.js +172 -0
- package/dist/modules/plugins/plugins.service.js.map +1 -0
- package/dist/modules/skills/pending-skills.service.d.ts +2 -2
- package/dist/modules/skills/pending-skills.service.js +7 -7
- package/dist/modules/skills/pending-skills.service.js.map +1 -1
- package/dist/modules/skills/skills.contract.d.ts +4 -4
- package/dist/modules/skills/skills.contract.d.ts.map +1 -1
- package/dist/modules/skills/skills.contract.js +1 -1
- package/dist/modules/skills/skills.service.js +5 -5
- package/dist/modules/skills/skills.service.js.map +1 -1
- package/dist/modules/tool-manuals/mcp-json-discovery.d.ts +65 -0
- package/dist/modules/tool-manuals/mcp-json-discovery.d.ts.map +1 -0
- package/dist/modules/tool-manuals/mcp-json-discovery.js +276 -0
- package/dist/modules/tool-manuals/mcp-json-discovery.js.map +1 -0
- package/dist/modules/tool-manuals/mcp-server-edit.service.d.ts +92 -0
- package/dist/modules/tool-manuals/mcp-server-edit.service.d.ts.map +1 -0
- package/dist/modules/tool-manuals/mcp-server-edit.service.js +328 -0
- package/dist/modules/tool-manuals/mcp-server-edit.service.js.map +1 -0
- package/dist/modules/tool-manuals/tool-manuals.contract.d.ts +38 -12
- package/dist/modules/tool-manuals/tool-manuals.contract.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.contract.js +1 -1
- package/dist/modules/tool-manuals/tool-manuals.routes.d.ts +13 -2
- package/dist/modules/tool-manuals/tool-manuals.routes.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.routes.js +233 -2
- package/dist/modules/tool-manuals/tool-manuals.routes.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.service.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.service.js +74 -37
- package/dist/modules/tool-manuals/tool-manuals.service.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.tools.js +6 -3
- package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
- package/dist/modules/workflow/git/git.service.js +2 -2
- package/dist/modules/workflow/git/git.service.js.map +1 -1
- package/dist/modules/workflow/pending-commits.service.d.ts +11 -0
- package/dist/modules/workflow/pending-commits.service.d.ts.map +1 -1
- package/dist/modules/workflow/pending-commits.service.js +37 -9
- package/dist/modules/workflow/pending-commits.service.js.map +1 -1
- package/dist/modules/workflow/pending-commits.worker.d.ts +17 -4
- package/dist/modules/workflow/pending-commits.worker.d.ts.map +1 -1
- package/dist/modules/workflow/pending-commits.worker.js +63 -9
- package/dist/modules/workflow/pending-commits.worker.js.map +1 -1
- package/dist/modules/workflow/workflow.service.d.ts +1 -0
- package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
- package/dist/modules/workflow/workflow.service.js +6 -1
- package/dist/modules/workflow/workflow.service.js.map +1 -1
- package/dist/modules/workspace/kb-seed.service.d.ts +2 -2
- package/dist/modules/workspace/kb-seed.service.d.ts.map +1 -1
- package/dist/modules/workspace/kb-seed.service.js +43 -10
- package/dist/modules/workspace/kb-seed.service.js.map +1 -1
- package/dist/modules/workspace/plugins-migration.d.ts +50 -0
- package/dist/modules/workspace/plugins-migration.d.ts.map +1 -0
- package/dist/modules/workspace/plugins-migration.js +379 -0
- package/dist/modules/workspace/plugins-migration.js.map +1 -0
- package/dist/modules/workspace/workspace.routes.js +3 -3
- package/dist/modules/workspace/workspace.routes.js.map +1 -1
- package/dist/modules/workspace/workspace.service.d.ts +26 -0
- package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.service.js +83 -12
- package/dist/modules/workspace/workspace.service.js.map +1 -1
- package/dist/shared/kb-layout.test.js +3 -3
- package/dist/shared/kb-layout.test.js.map +1 -1
- package/dist/shared/utcp-namespace.d.ts +6 -27
- package/dist/shared/utcp-namespace.d.ts.map +1 -1
- package/dist/shared/utcp-namespace.js +6 -63
- package/dist/shared/utcp-namespace.js.map +1 -1
- package/dist/shared/variable-refs.d.ts +42 -0
- package/dist/shared/variable-refs.d.ts.map +1 -0
- package/dist/shared/variable-refs.js +60 -0
- package/dist/shared/variable-refs.js.map +1 -0
- package/kb-template/.bevelignore +1 -1
- package/kb-template/AGENTS.md +88 -35
- package/kb-template/KnowledgeBase/How to get started.md +10 -10
- package/kb-template/access.md +36 -36
- package/migrations/meta/0000_snapshot.json +1479 -1479
- package/package.json +5 -4
- package/src/assets.ts +25 -25
- package/src/core/core-ports.ts +106 -106
- package/src/core/create-core-server.ts +55 -9
- package/src/core/create-core-services.ts +48 -24
- package/src/index.ts +69 -69
- package/src/modules/access/__tests__/access-control.atref-batch.test.ts +98 -98
- package/src/modules/access/__tests__/access-declarations.test.ts +28 -28
- package/src/modules/access/__tests__/access-md-format.test.ts +18 -18
- package/src/modules/access/__tests__/access-mutation.service.test.ts +5 -5
- package/src/modules/access/__tests__/access-splice.test.ts +2 -2
- package/src/modules/access/__tests__/access.routes.overrides.test.ts +16 -16
- package/src/modules/access/__tests__/grant-sources.test.ts +12 -12
- package/src/modules/access/__tests__/roles-admin.service.test.ts +13 -1
- package/src/modules/access/access-control.interface.ts +8 -7
- package/src/modules/access/access-control.service.ts +7 -7
- package/src/modules/access/access-declarations.ts +5 -5
- package/src/modules/access/access-mutation.service.ts +3 -3
- package/src/modules/access/access-splice.ts +4 -4
- package/src/modules/access/access.routes.ts +20 -20
- package/src/modules/access/creator-access.ts +5 -5
- package/src/modules/access/roles-admin.service.ts +13 -2
- package/src/modules/admin/admin-access.routes.ts +29 -29
- package/src/modules/auth/__tests__/auth.routes.test.ts +91 -91
- package/src/modules/auth/__tests__/rate-limit.test.ts +36 -36
- package/src/modules/auth/rate-limit.ts +45 -45
- package/src/modules/code-mode/__tests__/code-mode.tool.test.ts +67 -0
- package/src/modules/code-mode/code-mode-names.ts +10 -36
- package/src/modules/code-mode/code-mode.tool.ts +27 -7
- package/src/modules/database/connection.ts +15 -15
- package/src/modules/database/schema.ts +11 -11
- package/src/modules/diff/__tests__/diff.routes.rejectPathsLocked.test.ts +150 -150
- package/src/modules/mcp/mcp.service.ts +57 -435
- package/src/modules/{groups → plugins}/__tests__/join-proposals.test.ts +1 -1
- package/src/modules/{groups → plugins}/__tests__/join-requests.service.test.ts +7 -7
- package/src/modules/{groups/__tests__/group-index.service.test.ts → plugins/__tests__/plugin-index.service.test.ts} +41 -41
- package/src/modules/plugins/__tests__/plugin-provision.service.test.ts +312 -0
- package/src/modules/{groups/__tests__/groups.routes.test.ts → plugins/__tests__/plugins.routes.test.ts} +100 -100
- package/src/modules/plugins/index.ts +17 -0
- package/src/modules/{groups → plugins}/join-proposals.ts +2 -2
- package/src/modules/{groups → plugins}/join-requests.service.ts +8 -8
- package/src/modules/{groups/group-provision.service.ts → plugins/plugin-provision.service.ts} +139 -69
- package/src/modules/{groups/groups.contract.ts → plugins/plugins.contract.ts} +26 -26
- package/src/modules/{groups/groups.routes.ts → plugins/plugins.routes.ts} +102 -102
- package/src/modules/{groups/groups.service.ts → plugins/plugins.service.ts} +43 -43
- package/src/modules/secrets-vault/__tests__/tool-owner-gate.route.test.ts +143 -143
- package/src/modules/skills/__tests__/pending-skills.service.test.ts +14 -14
- package/src/modules/skills/__tests__/skills.service.test.ts +13 -13
- package/src/modules/skills/pending-skills.service.ts +7 -7
- package/src/modules/skills/skills.contract.ts +4 -4
- package/src/modules/skills/skills.service.ts +5 -5
- package/src/modules/tool-auth/llm-usage-meter.ts +19 -19
- package/src/modules/tool-manuals/__tests__/mcp-json-discovery.test.ts +198 -0
- package/src/modules/tool-manuals/__tests__/mcp-server-edit.service.test.ts +346 -0
- package/src/modules/tool-manuals/__tests__/tool-manuals.archive.route.test.ts +101 -0
- package/src/modules/tool-manuals/__tests__/tool-manuals.detail.route.test.ts +3 -3
- package/src/modules/tool-manuals/__tests__/tool-manuals.mcp-oauth.test.ts +2 -2
- package/src/modules/tool-manuals/__tests__/tool-manuals.service.test.ts +27 -27
- package/src/modules/tool-manuals/__tests__/tool-manuals.tools.test.ts +3 -3
- package/src/modules/tool-manuals/mcp-json-discovery.ts +328 -0
- package/src/modules/tool-manuals/mcp-server-edit.service.ts +434 -0
- package/src/modules/tool-manuals/tool-manuals.contract.ts +35 -12
- package/src/modules/tool-manuals/tool-manuals.routes.ts +222 -1
- package/src/modules/tool-manuals/tool-manuals.service.ts +82 -42
- package/src/modules/tool-manuals/tool-manuals.tools.ts +6 -3
- package/src/modules/workflow/__tests__/pending-commits.worker.test.ts +118 -0
- package/src/modules/workflow/__tests__/workflow.service.facade.test.ts +1 -1
- package/src/modules/workflow/git/__tests__/branch-name.test.ts +3 -3
- package/src/modules/workflow/git/__tests__/git.service.accessGating.test.ts +3 -3
- package/src/modules/workflow/git/__tests__/git.service.commitFile.test.ts +13 -8
- package/src/modules/workflow/git/__tests__/git.service.deleteBranch.test.ts +1 -1
- package/src/modules/workflow/git/__tests__/pull-request.service.test.ts +1 -1
- package/src/modules/workflow/git/git.service.ts +2 -2
- package/src/modules/workflow/pending-commits.service.ts +48 -12
- package/src/modules/workflow/pending-commits.worker.ts +64 -8
- package/src/modules/workflow/review-workflow/__tests__/approval-states.test.ts +1 -1
- package/src/modules/workflow/review-workflow/__tests__/cancel-pr.test.ts +1 -1
- package/src/modules/workflow/workflow-hooks.ts +101 -101
- package/src/modules/workflow/workflow.service.ts +13 -2
- package/src/modules/workspace/__tests__/kb-seed.service.test.ts +81 -13
- package/src/modules/workspace/__tests__/plugins-migration.test.ts +427 -0
- package/src/modules/workspace/__tests__/session-ontology.gate.test.ts +237 -237
- package/src/modules/workspace/__tests__/workspace.routes.create-grant.test.ts +236 -236
- package/src/modules/workspace/__tests__/workspace.routes.delete.test.ts +179 -179
- package/src/modules/workspace/__tests__/workspace.routes.download.test.ts +320 -320
- package/src/modules/workspace/__tests__/workspace.routes.read-gate.test.ts +337 -337
- package/src/modules/workspace/__tests__/workspace.service.test.ts +116 -0
- package/src/modules/workspace/bevel-ignore.ts +66 -66
- package/src/modules/workspace/kb-seed.service.ts +38 -9
- package/src/modules/workspace/plugins-migration.ts +479 -0
- package/src/modules/workspace/session-sink.ts +25 -25
- package/src/modules/workspace/workspace.routes.ts +3 -3
- package/src/modules/workspace/workspace.service.ts +85 -14
- package/src/modules/workspace/workspace.tools.ts +922 -922
- package/src/shared/__tests__/join-request.test.ts +13 -13
- package/src/shared/__tests__/kb-layout.plugin.test.ts +45 -0
- package/src/shared/kb-layout.test.ts +3 -3
- package/src/shared/utcp-namespace.ts +10 -68
- package/src/shared/variable-refs.ts +64 -0
- package/src/modules/groups/__tests__/group-provision.service.test.ts +0 -247
- package/src/modules/groups/index.ts +0 -17
- package/src/shared/__tests__/kb-layout.group.test.ts +0 -45
- /package/kb-template/{Groups → Plugins}/.gitkeep +0 -0
|
@@ -13,18 +13,18 @@ import type { WorkspaceService } from '../../workspace/workspace.service.js';
|
|
|
13
13
|
import type { IAccessControl } from '../../access/access-control.interface.js';
|
|
14
14
|
import type { ISkillService, SkillSummary } from '../../skills/skills.contract.js';
|
|
15
15
|
import type { IToolManualService, ToolManualSummary } from '../../tool-manuals/tool-manuals.contract.js';
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
16
|
+
import { PluginIndexService } from '../plugins.service.js';
|
|
17
|
+
import { createPluginsRoutes } from '../plugins.routes.js';
|
|
18
18
|
import type { JoinRequestsService } from '../join-requests.service.js';
|
|
19
|
-
import type {
|
|
19
|
+
import type { PluginSummary, IPluginIndexService } from '../plugins.contract.js';
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
|
-
* HTTP-level contract for the
|
|
22
|
+
* HTTP-level contract for the plugin routes: the auth gate, the three-tier
|
|
23
23
|
* enumeration (member / manager / discoverable — all ordinary access
|
|
24
|
-
* verdicts), the fail-closed omission of
|
|
24
|
+
* verdicts), the fail-closed omission of plugins with no verdict at all, and
|
|
25
25
|
* the join flow that rides on plain change requests.
|
|
26
26
|
*
|
|
27
|
-
* The
|
|
27
|
+
* The plugin index is REAL over a temp KB; access control and the workflow are
|
|
28
28
|
* stubs, because what's under test here is the route's use of them.
|
|
29
29
|
*/
|
|
30
30
|
|
|
@@ -38,18 +38,18 @@ const tmpDirs: string[] = [];
|
|
|
38
38
|
|
|
39
39
|
interface HarnessOpts {
|
|
40
40
|
/**
|
|
41
|
-
* Paths (`
|
|
41
|
+
* Paths (`Plugins/GTM` for the folder/member verdict, `Plugins/GTM/access.md`
|
|
42
42
|
* for the discover verdict) the given email may read.
|
|
43
43
|
*/
|
|
44
44
|
readable?: Record<string, string[]>;
|
|
45
45
|
/** Paths the given email may write. */
|
|
46
46
|
writable?: Record<string, string[]>;
|
|
47
|
-
/** Paths (the FOLDER, e.g. `
|
|
47
|
+
/** Paths (the FOLDER, e.g. `Plugins/GTM`) the given email OWNS. */
|
|
48
48
|
owner?: Record<string, string[]>;
|
|
49
49
|
/** Open CRs `listChangeRequestsAuthoredBy` returns for any caller. */
|
|
50
50
|
authoredCrs?: ChangeRequest[];
|
|
51
51
|
/** Override the whole index (used to force the 500 path). */
|
|
52
|
-
index?:
|
|
52
|
+
index?: IPluginIndexService;
|
|
53
53
|
email?: string | null;
|
|
54
54
|
skills?: SkillSummary[];
|
|
55
55
|
tools?: ToolManualSummary[];
|
|
@@ -72,18 +72,18 @@ function cr(over: Partial<ChangeRequest>): ChangeRequest {
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
async function makeHarness(opts: HarnessOpts = {}) {
|
|
75
|
-
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), 'bevel-
|
|
75
|
+
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), 'bevel-plugins-routes-'));
|
|
76
76
|
tmpDirs.push(workspaceDir);
|
|
77
77
|
const kbRoot = path.join(workspaceDir, KB);
|
|
78
|
-
// Both carry the access.md that makes a folder a
|
|
79
|
-
await fs.mkdir(path.join(kbRoot, '
|
|
78
|
+
// Both carry the access.md that makes a folder a plugin at all.
|
|
79
|
+
await fs.mkdir(path.join(kbRoot, 'Plugins', 'GTM'), { recursive: true });
|
|
80
80
|
await fs.writeFile(
|
|
81
|
-
path.join(kbRoot, '
|
|
81
|
+
path.join(kbRoot, 'Plugins', 'GTM', 'access.md'),
|
|
82
82
|
'---\nread:\n - everyone\n---\nread: []\n',
|
|
83
83
|
);
|
|
84
|
-
await fs.mkdir(path.join(kbRoot, '
|
|
84
|
+
await fs.mkdir(path.join(kbRoot, 'Plugins', 'Finance'), { recursive: true });
|
|
85
85
|
await fs.writeFile(
|
|
86
|
-
path.join(kbRoot, '
|
|
86
|
+
path.join(kbRoot, 'Plugins', 'Finance', 'access.md'),
|
|
87
87
|
'---\nread:\n - everyone\n---\nread: []\n',
|
|
88
88
|
);
|
|
89
89
|
|
|
@@ -133,7 +133,7 @@ async function makeHarness(opts: HarnessOpts = {}) {
|
|
|
133
133
|
|
|
134
134
|
const index =
|
|
135
135
|
opts.index ??
|
|
136
|
-
new
|
|
136
|
+
new PluginIndexService(workspaceService, accessControl, skillService, toolService, KB);
|
|
137
137
|
|
|
138
138
|
const email = opts.email === undefined ? ALI : opts.email;
|
|
139
139
|
const app = express();
|
|
@@ -153,13 +153,13 @@ async function makeHarness(opts: HarnessOpts = {}) {
|
|
|
153
153
|
// Provisioning MECHANISM is exercised by its own service tests; the routes
|
|
154
154
|
// here only need to prove what they hand it and when they refuse to.
|
|
155
155
|
const provision = {
|
|
156
|
-
|
|
157
|
-
|
|
156
|
+
createPlugin: vi.fn(async () => ({ folder: 'GTM', created: true })),
|
|
157
|
+
deletePlugin: vi.fn(async () => undefined),
|
|
158
158
|
};
|
|
159
159
|
|
|
160
160
|
app.use(
|
|
161
161
|
'/api',
|
|
162
|
-
|
|
162
|
+
createPluginsRoutes(
|
|
163
163
|
index,
|
|
164
164
|
accessControl,
|
|
165
165
|
workflow,
|
|
@@ -191,10 +191,10 @@ function close(s: Server): Promise<void> {
|
|
|
191
191
|
}
|
|
192
192
|
|
|
193
193
|
const MEMBER_OF_BOTH = {
|
|
194
|
-
[ALI]: ['
|
|
194
|
+
[ALI]: ['Plugins/GTM', 'Plugins/GTM/access.md', 'Plugins/Finance', 'Plugins/Finance/access.md'],
|
|
195
195
|
};
|
|
196
196
|
|
|
197
|
-
describe('/api/
|
|
197
|
+
describe('/api/plugins routes', () => {
|
|
198
198
|
let server: Server | null = null;
|
|
199
199
|
afterEach(async () => {
|
|
200
200
|
if (server) await close(server);
|
|
@@ -202,51 +202,51 @@ describe('/api/groups routes', () => {
|
|
|
202
202
|
await Promise.all(tmpDirs.splice(0).map((d) => fs.rm(d, { recursive: true, force: true })));
|
|
203
203
|
});
|
|
204
204
|
|
|
205
|
-
const
|
|
206
|
-
const res = await fetch(`${baseUrl}/api/
|
|
207
|
-
const body = (await res.json()) as {
|
|
208
|
-
return { status: res.status,
|
|
205
|
+
const listPlugins = async (baseUrl: string) => {
|
|
206
|
+
const res = await fetch(`${baseUrl}/api/plugins`);
|
|
207
|
+
const body = (await res.json()) as { plugins: PluginSummary[] };
|
|
208
|
+
return { status: res.status, plugins: body.plugins, raw: JSON.stringify(body) };
|
|
209
209
|
};
|
|
210
210
|
|
|
211
211
|
it('401s both endpoints when req.userEmail is absent', async () => {
|
|
212
212
|
const h = await makeHarness({ email: null });
|
|
213
213
|
server = h.server;
|
|
214
214
|
for (const [method, url] of [
|
|
215
|
-
['GET', '/api/
|
|
216
|
-
['POST', '/api/
|
|
217
|
-
['DELETE', '/api/
|
|
215
|
+
['GET', '/api/plugins'],
|
|
216
|
+
['POST', '/api/plugins/GTM/join-request'],
|
|
217
|
+
['DELETE', '/api/plugins/GTM'],
|
|
218
218
|
] as const) {
|
|
219
219
|
const res = await fetch(`${h.baseUrl}${url}`, { method });
|
|
220
220
|
expect(res.status, `${method} ${url}`).toBe(401);
|
|
221
221
|
}
|
|
222
222
|
});
|
|
223
223
|
|
|
224
|
-
it('lists member
|
|
224
|
+
it('lists member plugins sorted, counting by pluginOfPath', async () => {
|
|
225
225
|
const h = await makeHarness({
|
|
226
226
|
readable: MEMBER_OF_BOTH,
|
|
227
|
-
skills: [{ name: 'outreach', description: '', path: '
|
|
227
|
+
skills: [{ name: 'outreach', description: '', path: 'Plugins/GTM/outreach' }],
|
|
228
228
|
tools: [
|
|
229
|
-
{ slug: 'ledger', name: 'ledger', path: '
|
|
230
|
-
{ slug: 'slack', name: 'slack', path: '
|
|
229
|
+
{ slug: 'ledger', name: 'ledger', path: 'Plugins/Finance/ledger.tool', type: 'inline' },
|
|
230
|
+
{ slug: 'slack', name: 'slack', path: 'Plugins/slack.tool', type: 'inline' },
|
|
231
231
|
],
|
|
232
232
|
});
|
|
233
233
|
server = h.server;
|
|
234
|
-
const { status,
|
|
234
|
+
const { status, plugins } = await listPlugins(h.baseUrl);
|
|
235
235
|
expect(status).toBe(200);
|
|
236
|
-
expect(
|
|
237
|
-
expect(
|
|
238
|
-
expect(
|
|
236
|
+
expect(plugins.map((g) => g.name)).toEqual(['Finance', 'GTM']);
|
|
237
|
+
expect(plugins[0]).toMatchObject({ canRead: true, skillCount: 0, toolCount: 1 });
|
|
238
|
+
expect(plugins[1]).toMatchObject({ canRead: true, skillCount: 1, toolCount: 0 });
|
|
239
239
|
});
|
|
240
240
|
|
|
241
|
-
it('a DISCOVERABLE
|
|
241
|
+
it('a DISCOVERABLE plugin (access.md readable, folder not) lists locked with hasRequested from the join CR', async () => {
|
|
242
242
|
const h = await makeHarness({
|
|
243
|
-
readable: { [ALI]: ['
|
|
243
|
+
readable: { [ALI]: ['Plugins/Finance/access.md'] },
|
|
244
244
|
authoredCrs: [cr({ branch: joinBranchFor(ALI, 'Finance'), number: 9 })],
|
|
245
245
|
});
|
|
246
246
|
server = h.server;
|
|
247
|
-
const {
|
|
248
|
-
expect(
|
|
249
|
-
expect(
|
|
247
|
+
const { plugins } = await listPlugins(h.baseUrl);
|
|
248
|
+
expect(plugins.map((g) => g.name)).toEqual(['Finance']);
|
|
249
|
+
expect(plugins[0]).toMatchObject({
|
|
250
250
|
canRead: false,
|
|
251
251
|
canWrite: false,
|
|
252
252
|
hasRequested: true,
|
|
@@ -254,91 +254,91 @@ describe('/api/groups routes', () => {
|
|
|
254
254
|
});
|
|
255
255
|
});
|
|
256
256
|
|
|
257
|
-
it('OMITS a
|
|
258
|
-
const h = await makeHarness({ readable: { [ALI]: ['
|
|
257
|
+
it('OMITS a plugin with NO verdict at all — nothing about it leaves the backend', async () => {
|
|
258
|
+
const h = await makeHarness({ readable: { [ALI]: ['Plugins/Finance/access.md'] } });
|
|
259
259
|
server = h.server;
|
|
260
|
-
const {
|
|
261
|
-
expect(
|
|
260
|
+
const { plugins, raw } = await listPlugins(h.baseUrl);
|
|
261
|
+
expect(plugins.map((g) => g.name)).toEqual(['Finance']);
|
|
262
262
|
expect(raw).not.toContain('"name":"GTM"');
|
|
263
|
-
expect(raw).not.toContain('
|
|
263
|
+
expect(raw).not.toContain('Plugins/GTM');
|
|
264
264
|
});
|
|
265
265
|
|
|
266
|
-
it("keeps a locked-out folder-writer's
|
|
267
|
-
const h = await makeHarness({ writable: { [ALI]: ['
|
|
266
|
+
it("keeps a locked-out folder-writer's plugin listed with canWrite: true (admin-rescue)", async () => {
|
|
267
|
+
const h = await makeHarness({ writable: { [ALI]: ['Plugins/GTM/access.md'] } });
|
|
268
268
|
server = h.server;
|
|
269
|
-
const {
|
|
270
|
-
expect(
|
|
271
|
-
expect(
|
|
269
|
+
const { plugins } = await listPlugins(h.baseUrl);
|
|
270
|
+
expect(plugins.map((g) => g.name)).toEqual(['GTM']);
|
|
271
|
+
expect(plugins[0]).toMatchObject({ canRead: false, canWrite: true });
|
|
272
272
|
});
|
|
273
273
|
|
|
274
274
|
it('a member never reports hasRequested (their stale join CR is ignored)', async () => {
|
|
275
275
|
const h = await makeHarness({
|
|
276
|
-
readable: { [ALI]: ['
|
|
276
|
+
readable: { [ALI]: ['Plugins/GTM', 'Plugins/GTM/access.md'] },
|
|
277
277
|
authoredCrs: [cr({})],
|
|
278
278
|
});
|
|
279
279
|
server = h.server;
|
|
280
|
-
const {
|
|
281
|
-
expect(
|
|
280
|
+
const { plugins } = await listPlugins(h.baseUrl);
|
|
281
|
+
expect(plugins[0]).toMatchObject({ name: 'GTM', canRead: true, hasRequested: false });
|
|
282
282
|
});
|
|
283
283
|
|
|
284
284
|
it('lists the owner verdict per caller — the folder verdict, not the manager one', async () => {
|
|
285
285
|
const h = await makeHarness({
|
|
286
286
|
readable: MEMBER_OF_BOTH,
|
|
287
|
-
owner: { [ALI]: ['
|
|
287
|
+
owner: { [ALI]: ['Plugins/GTM'] },
|
|
288
288
|
});
|
|
289
289
|
server = h.server;
|
|
290
|
-
const {
|
|
291
|
-
expect(
|
|
292
|
-
expect(
|
|
290
|
+
const { plugins } = await listPlugins(h.baseUrl);
|
|
291
|
+
expect(plugins.find((g) => g.name === 'GTM')).toMatchObject({ isOwner: true });
|
|
292
|
+
expect(plugins.find((g) => g.name === 'Finance')).toMatchObject({ isOwner: false });
|
|
293
293
|
});
|
|
294
294
|
|
|
295
295
|
it('delete: 404 for unknown AND for a non-owner — a manager included (identical, fail-closed)', async () => {
|
|
296
296
|
// A MANAGER (write on the access.md) and a MEMBER, but not an owner:
|
|
297
297
|
// deletion is the owner's verb, and the refusal must not confirm the
|
|
298
|
-
//
|
|
298
|
+
// plugin exists.
|
|
299
299
|
const h = await makeHarness({
|
|
300
300
|
readable: MEMBER_OF_BOTH,
|
|
301
|
-
writable: { [ALI]: ['
|
|
301
|
+
writable: { [ALI]: ['Plugins/GTM/access.md'] },
|
|
302
302
|
});
|
|
303
303
|
server = h.server;
|
|
304
304
|
for (const name of ['Nope', 'GTM']) {
|
|
305
|
-
const res = await fetch(`${h.baseUrl}/api/
|
|
305
|
+
const res = await fetch(`${h.baseUrl}/api/plugins/${name}`, { method: 'DELETE' });
|
|
306
306
|
expect(res.status, name).toBe(404);
|
|
307
|
-
expect(await res.json()).toEqual({ error: 'Unknown
|
|
307
|
+
expect(await res.json()).toEqual({ error: 'Unknown plugin', kind: 'unknown-plugin' });
|
|
308
308
|
}
|
|
309
|
-
expect(h.provision.
|
|
309
|
+
expect(h.provision.deletePlugin).not.toHaveBeenCalled();
|
|
310
310
|
});
|
|
311
311
|
|
|
312
312
|
it('delete: an OWNER deletes through the provision door, by the catalog name', async () => {
|
|
313
|
-
const h = await makeHarness({ owner: { [ALI]: ['
|
|
313
|
+
const h = await makeHarness({ owner: { [ALI]: ['Plugins/GTM'] } });
|
|
314
314
|
server = h.server;
|
|
315
|
-
const res = await fetch(`${h.baseUrl}/api/
|
|
315
|
+
const res = await fetch(`${h.baseUrl}/api/plugins/GTM`, { method: 'DELETE' });
|
|
316
316
|
expect(res.status).toBe(200);
|
|
317
317
|
expect(await res.json()).toEqual({ ok: true });
|
|
318
|
-
expect(h.provision.
|
|
318
|
+
expect(h.provision.deletePlugin).toHaveBeenCalledWith(
|
|
319
319
|
expect.objectContaining({ email: ALI }),
|
|
320
320
|
'GTM',
|
|
321
321
|
);
|
|
322
322
|
});
|
|
323
323
|
|
|
324
324
|
it("delete: passes a provision refusal through with the service's own status and words", async () => {
|
|
325
|
-
const h = await makeHarness({ owner: { [ALI]: ['
|
|
326
|
-
const {
|
|
327
|
-
h.provision.
|
|
325
|
+
const h = await makeHarness({ owner: { [ALI]: ['Plugins/GTM'] } });
|
|
326
|
+
const { PluginProvisionError } = await import('../plugin-provision.service.js');
|
|
327
|
+
h.provision.deletePlugin.mockRejectedValueOnce(new PluginProvisionError('Unknown plugin', 404));
|
|
328
328
|
server = h.server;
|
|
329
|
-
const res = await fetch(`${h.baseUrl}/api/
|
|
329
|
+
const res = await fetch(`${h.baseUrl}/api/plugins/GTM`, { method: 'DELETE' });
|
|
330
330
|
expect(res.status).toBe(404);
|
|
331
|
-
expect(await res.json()).toEqual({ error: 'Unknown
|
|
331
|
+
expect(await res.json()).toEqual({ error: 'Unknown plugin' });
|
|
332
332
|
});
|
|
333
333
|
|
|
334
334
|
it('delete: 500s with its own words when the mechanism fails', async () => {
|
|
335
335
|
const error = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
336
|
-
const h = await makeHarness({ owner: { [ALI]: ['
|
|
337
|
-
h.provision.
|
|
336
|
+
const h = await makeHarness({ owner: { [ALI]: ['Plugins/GTM'] } });
|
|
337
|
+
h.provision.deletePlugin.mockRejectedValueOnce(new Error('push refused'));
|
|
338
338
|
server = h.server;
|
|
339
|
-
const res = await fetch(`${h.baseUrl}/api/
|
|
339
|
+
const res = await fetch(`${h.baseUrl}/api/plugins/GTM`, { method: 'DELETE' });
|
|
340
340
|
expect(res.status).toBe(500);
|
|
341
|
-
expect(await res.json()).toEqual({ error: 'Failed to delete the
|
|
341
|
+
expect(await res.json()).toEqual({ error: 'Failed to delete the plugin' });
|
|
342
342
|
error.mockRestore();
|
|
343
343
|
});
|
|
344
344
|
|
|
@@ -346,33 +346,33 @@ describe('/api/groups routes', () => {
|
|
|
346
346
|
const h = await makeHarness({});
|
|
347
347
|
server = h.server;
|
|
348
348
|
for (const name of ['Nope', 'GTM']) {
|
|
349
|
-
const res = await fetch(`${h.baseUrl}/api/
|
|
349
|
+
const res = await fetch(`${h.baseUrl}/api/plugins/${name}/join-request`, { method: 'POST' });
|
|
350
350
|
expect(res.status, name).toBe(404);
|
|
351
|
-
expect(await res.json()).toEqual({ error: 'Unknown
|
|
351
|
+
expect(await res.json()).toEqual({ error: 'Unknown plugin', kind: 'unknown-plugin' });
|
|
352
352
|
}
|
|
353
353
|
});
|
|
354
354
|
|
|
355
355
|
it('join-request: 409 when the caller can already read the folder', async () => {
|
|
356
356
|
const h = await makeHarness({ readable: MEMBER_OF_BOTH });
|
|
357
357
|
server = h.server;
|
|
358
|
-
const res = await fetch(`${h.baseUrl}/api/
|
|
358
|
+
const res = await fetch(`${h.baseUrl}/api/plugins/Finance/join-request`, { method: 'POST' });
|
|
359
359
|
expect(res.status).toBe(409);
|
|
360
360
|
expect((await res.json()).kind).toBe('already-readable');
|
|
361
361
|
});
|
|
362
362
|
|
|
363
363
|
it('join-request: branch + splice + commit + CR, on the deterministic join branch', async () => {
|
|
364
|
-
const h = await makeHarness({ readable: { [ALI]: ['
|
|
364
|
+
const h = await makeHarness({ readable: { [ALI]: ['Plugins/Finance/access.md'] } });
|
|
365
365
|
server = h.server;
|
|
366
|
-
const res = await fetch(`${h.baseUrl}/api/
|
|
366
|
+
const res = await fetch(`${h.baseUrl}/api/plugins/Finance/join-request`, { method: 'POST' });
|
|
367
367
|
expect(res.status).toBe(200);
|
|
368
368
|
expect(await res.json()).toEqual({ ok: true, number: 42 });
|
|
369
369
|
|
|
370
370
|
const branch = joinBranchFor(ALI, 'Finance');
|
|
371
371
|
expect(h.workflow.createBranch).toHaveBeenCalledWith(wsId, branch, DEFAULT_BRANCH);
|
|
372
|
-
// The write went to the
|
|
372
|
+
// The write went to the plugin's access.md and added the caller to the
|
|
373
373
|
// BODY's read list (folder rules), leaving the discovery frontmatter alone.
|
|
374
374
|
const write = (h.workspaceService.writeFile as ReturnType<typeof vi.fn>).mock.calls[0];
|
|
375
|
-
expect(write[1]).toBe(`${KB}/
|
|
375
|
+
expect(write[1]).toBe(`${KB}/Plugins/Finance/access.md`);
|
|
376
376
|
expect(write[2]).toContain('Ali Baba <ali@bevel.software>');
|
|
377
377
|
expect((write[2] as string).indexOf('everyone')).toBeLessThan(
|
|
378
378
|
(write[2] as string).indexOf('Ali Baba'),
|
|
@@ -391,11 +391,11 @@ describe('/api/groups routes', () => {
|
|
|
391
391
|
|
|
392
392
|
it('join-request is idempotent: an existing open join CR is returned, nothing new is created', async () => {
|
|
393
393
|
const h = await makeHarness({
|
|
394
|
-
readable: { [ALI]: ['
|
|
394
|
+
readable: { [ALI]: ['Plugins/Finance/access.md'] },
|
|
395
395
|
authoredCrs: [cr({ branch: joinBranchFor(ALI, 'Finance'), number: 9 })],
|
|
396
396
|
});
|
|
397
397
|
server = h.server;
|
|
398
|
-
const res = await fetch(`${h.baseUrl}/api/
|
|
398
|
+
const res = await fetch(`${h.baseUrl}/api/plugins/Finance/join-request`, { method: 'POST' });
|
|
399
399
|
expect(res.status).toBe(200);
|
|
400
400
|
expect(await res.json()).toEqual({ ok: true, number: 9 });
|
|
401
401
|
expect(h.workflow.createBranch).not.toHaveBeenCalled();
|
|
@@ -403,27 +403,27 @@ describe('/api/groups routes', () => {
|
|
|
403
403
|
});
|
|
404
404
|
|
|
405
405
|
it('degrades hasRequested to false when the CR lookup throws', async () => {
|
|
406
|
-
const h = await makeHarness({ readable: { [ALI]: ['
|
|
406
|
+
const h = await makeHarness({ readable: { [ALI]: ['Plugins/Finance/access.md'] } });
|
|
407
407
|
(h.workflow.listChangeRequestsAuthoredBy as ReturnType<typeof vi.fn>).mockRejectedValueOnce(
|
|
408
408
|
new Error('gh down'),
|
|
409
409
|
);
|
|
410
410
|
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
411
411
|
server = h.server;
|
|
412
|
-
const { status,
|
|
412
|
+
const { status, plugins } = await listPlugins(h.baseUrl);
|
|
413
413
|
expect(status).toBe(200);
|
|
414
|
-
expect(
|
|
414
|
+
expect(plugins[0]).toMatchObject({ hasRequested: false });
|
|
415
415
|
warn.mockRestore();
|
|
416
416
|
});
|
|
417
417
|
|
|
418
|
-
it('join-requests: a MANAGER gets the service\'s list for the
|
|
419
|
-
const h = await makeHarness({ writable: { [ALI]: ['
|
|
418
|
+
it('join-requests: a MANAGER gets the service\'s list for the plugin', async () => {
|
|
419
|
+
const h = await makeHarness({ writable: { [ALI]: ['Plugins/GTM/access.md'] } });
|
|
420
420
|
server = h.server;
|
|
421
|
-
const res = await fetch(`${h.baseUrl}/api/
|
|
421
|
+
const res = await fetch(`${h.baseUrl}/api/plugins/GTM/join-requests`);
|
|
422
422
|
expect(res.status).toBe(200);
|
|
423
423
|
expect(await res.json()).toEqual({ requests: [] });
|
|
424
424
|
expect(h.joinRequests.list).toHaveBeenCalledWith(
|
|
425
425
|
'GTM',
|
|
426
|
-
'
|
|
426
|
+
'Plugins/GTM',
|
|
427
427
|
expect.anything(),
|
|
428
428
|
expect.objectContaining({ email: ALI }),
|
|
429
429
|
);
|
|
@@ -434,7 +434,7 @@ describe('/api/groups routes', () => {
|
|
|
434
434
|
// question only the server answers.
|
|
435
435
|
const h = await makeHarness({ readable: MEMBER_OF_BOTH });
|
|
436
436
|
server = h.server;
|
|
437
|
-
const res = await fetch(`${h.baseUrl}/api/
|
|
437
|
+
const res = await fetch(`${h.baseUrl}/api/plugins/GTM/join-requests`);
|
|
438
438
|
expect(res.status).toBe(200);
|
|
439
439
|
expect(await res.json()).toEqual({ requests: [] });
|
|
440
440
|
expect(h.joinRequests.list).not.toHaveBeenCalled();
|
|
@@ -443,7 +443,7 @@ describe('/api/groups routes', () => {
|
|
|
443
443
|
it('reconcile: 404 for a non-manager — indistinguishable from a missing request', async () => {
|
|
444
444
|
const h = await makeHarness({ readable: MEMBER_OF_BOTH, authoredCrs: [cr({ number: 7 })] });
|
|
445
445
|
server = h.server;
|
|
446
|
-
const denied = await fetch(`${h.baseUrl}/api/
|
|
446
|
+
const denied = await fetch(`${h.baseUrl}/api/plugins/GTM/join-requests/7/reconcile`, {
|
|
447
447
|
method: 'POST',
|
|
448
448
|
});
|
|
449
449
|
expect(denied.status).toBe(404);
|
|
@@ -451,9 +451,9 @@ describe('/api/groups routes', () => {
|
|
|
451
451
|
});
|
|
452
452
|
|
|
453
453
|
it('reconcile: 404 for a manager when the change request does not exist', async () => {
|
|
454
|
-
const h = await makeHarness({ writable: { [ALI]: ['
|
|
454
|
+
const h = await makeHarness({ writable: { [ALI]: ['Plugins/GTM/access.md'] } });
|
|
455
455
|
server = h.server;
|
|
456
|
-
const missing = await fetch(`${h.baseUrl}/api/
|
|
456
|
+
const missing = await fetch(`${h.baseUrl}/api/plugins/GTM/join-requests/999/reconcile`, {
|
|
457
457
|
method: 'POST',
|
|
458
458
|
});
|
|
459
459
|
expect(missing.status).toBe(404);
|
|
@@ -462,19 +462,19 @@ describe('/api/groups routes', () => {
|
|
|
462
462
|
|
|
463
463
|
it('reconcile: a manager settles an open request through the service', async () => {
|
|
464
464
|
const h = await makeHarness({
|
|
465
|
-
writable: { [ALI]: ['
|
|
465
|
+
writable: { [ALI]: ['Plugins/GTM/access.md'] },
|
|
466
466
|
authoredCrs: [cr({ number: 7 })],
|
|
467
467
|
});
|
|
468
468
|
(h.joinRequests.reconcile as ReturnType<typeof vi.fn>).mockResolvedValue(true);
|
|
469
469
|
server = h.server;
|
|
470
|
-
const res = await fetch(`${h.baseUrl}/api/
|
|
470
|
+
const res = await fetch(`${h.baseUrl}/api/plugins/GTM/join-requests/7/reconcile`, {
|
|
471
471
|
method: 'POST',
|
|
472
472
|
});
|
|
473
473
|
expect(res.status).toBe(200);
|
|
474
474
|
expect(await res.json()).toEqual({ closed: true });
|
|
475
475
|
});
|
|
476
476
|
|
|
477
|
-
it('500s with { error: "Failed to list
|
|
477
|
+
it('500s with { error: "Failed to list plugins" } when the index throws', async () => {
|
|
478
478
|
const error = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
479
479
|
const h = await makeHarness({
|
|
480
480
|
index: {
|
|
@@ -485,9 +485,9 @@ describe('/api/groups routes', () => {
|
|
|
485
485
|
},
|
|
486
486
|
});
|
|
487
487
|
server = h.server;
|
|
488
|
-
const res = await fetch(`${h.baseUrl}/api/
|
|
488
|
+
const res = await fetch(`${h.baseUrl}/api/plugins`);
|
|
489
489
|
expect(res.status).toBe(500);
|
|
490
|
-
expect(await res.json()).toEqual({ error: 'Failed to list
|
|
490
|
+
expect(await res.json()).toEqual({ error: 'Failed to list plugins' });
|
|
491
491
|
error.mockRestore();
|
|
492
492
|
});
|
|
493
493
|
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export { PluginIndexService, pluginsWorkspaceId } from './plugins.service.js';
|
|
2
|
+
export {
|
|
3
|
+
PluginProvisionService,
|
|
4
|
+
PluginProvisionError,
|
|
5
|
+
pluginAccessMd,
|
|
6
|
+
personalAccessMd,
|
|
7
|
+
} from './plugin-provision.service.js';
|
|
8
|
+
export { JoinRequestsService, type JoinRequest } from './join-requests.service.js';
|
|
9
|
+
export { pendingProposals, type JoinProposal } from './join-proposals.js';
|
|
10
|
+
export { createPluginsRoutes } from './plugins.routes.js';
|
|
11
|
+
export type {
|
|
12
|
+
IPluginIndexService,
|
|
13
|
+
PluginCatalogEntry,
|
|
14
|
+
PluginSummary,
|
|
15
|
+
ResolvedPrincipals,
|
|
16
|
+
ResolvedReaders,
|
|
17
|
+
} from './plugins.contract.js';
|
|
@@ -8,9 +8,9 @@ import {
|
|
|
8
8
|
/**
|
|
9
9
|
* What a join branch is PROPOSING, relative to the default branch.
|
|
10
10
|
*
|
|
11
|
-
* A join request is a change request whose branch edits one
|
|
11
|
+
* A join request is a change request whose branch edits one plugin's
|
|
12
12
|
* `access.md`. Rather than treating that change request as an all-or-nothing
|
|
13
|
-
* merge, the
|
|
13
|
+
* merge, the plugin's managers see its individual proposals — "grant Ali read",
|
|
14
14
|
* "grant GTM Team write" — and answer them one at a time. Approving a proposal
|
|
15
15
|
* writes THAT ONE grant onto the default branch through the ordinary access
|
|
16
16
|
* mutation path; the branch is never merged, so nothing else it happens to
|
|
@@ -45,15 +45,15 @@ export class JoinRequestsService {
|
|
|
45
45
|
) {}
|
|
46
46
|
|
|
47
47
|
/**
|
|
48
|
-
* Open join requests for `
|
|
48
|
+
* Open join requests for `plugin`, each carrying only its still-pending
|
|
49
49
|
* proposals. Requests with none left are reconciled away (rejected, branch
|
|
50
50
|
* deleted) and omitted.
|
|
51
51
|
*
|
|
52
|
-
* `actor` is who the reconciliation acts as — a manager of the
|
|
52
|
+
* `actor` is who the reconciliation acts as — a manager of the plugin, since
|
|
53
53
|
* rejecting a change request requires write on everything it touches.
|
|
54
54
|
*/
|
|
55
55
|
async list(
|
|
56
|
-
|
|
56
|
+
plugin: string,
|
|
57
57
|
folder: string,
|
|
58
58
|
crs: ChangeRequest[],
|
|
59
59
|
actor: AuthUser,
|
|
@@ -63,7 +63,7 @@ export class JoinRequestsService {
|
|
|
63
63
|
const baseText = await this.readAt(DEFAULT_BRANCH, accessPath);
|
|
64
64
|
const out: JoinRequest[] = [];
|
|
65
65
|
for (const cr of crs) {
|
|
66
|
-
if (cr.state !== 'open' || !isJoinBranchFor(cr.branch,
|
|
66
|
+
if (cr.state !== 'open' || !isJoinBranchFor(cr.branch, plugin)) continue;
|
|
67
67
|
const proposals = pendingProposals(
|
|
68
68
|
await this.readAt(cr.branch, accessPath),
|
|
69
69
|
baseText,
|
|
@@ -90,12 +90,12 @@ export class JoinRequestsService {
|
|
|
90
90
|
* same round-trip instead of waiting for the next listing.
|
|
91
91
|
*/
|
|
92
92
|
async reconcile(
|
|
93
|
-
|
|
93
|
+
plugin: string,
|
|
94
94
|
folder: string,
|
|
95
95
|
cr: ChangeRequest,
|
|
96
96
|
actor: AuthUser,
|
|
97
97
|
): Promise<boolean> {
|
|
98
|
-
if (cr.state !== 'open' || !isJoinBranchFor(cr.branch,
|
|
98
|
+
if (cr.state !== 'open' || !isJoinBranchFor(cr.branch, plugin)) return false;
|
|
99
99
|
await this.refresh();
|
|
100
100
|
const accessPath = `${folder}/access.md`;
|
|
101
101
|
const proposals = pendingProposals(
|
|
@@ -133,7 +133,7 @@ export class JoinRequestsService {
|
|
|
133
133
|
);
|
|
134
134
|
} catch (err) {
|
|
135
135
|
console.warn(
|
|
136
|
-
`[
|
|
136
|
+
`[plugins] could not close settled join request #${cr.number}: ${
|
|
137
137
|
err instanceof Error ? err.message : String(err)
|
|
138
138
|
}`,
|
|
139
139
|
);
|
|
@@ -145,7 +145,7 @@ export class JoinRequestsService {
|
|
|
145
145
|
// The request is closed either way; a leftover branch is cosmetic and
|
|
146
146
|
// the author can still delete it themselves.
|
|
147
147
|
console.warn(
|
|
148
|
-
`[
|
|
148
|
+
`[plugins] closed join request #${cr.number} but could not delete "${cr.branch}": ${
|
|
149
149
|
err instanceof Error ? err.message : String(err)
|
|
150
150
|
}`,
|
|
151
151
|
);
|