@bevel-software/platform-core-backend 0.7.5 → 0.9.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 +8 -1
- package/dist/core/core-ports.d.ts.map +1 -1
- package/dist/core/core-ports.js.map +1 -1
- package/dist/core/create-core-server.d.ts.map +1 -1
- package/dist/core/create-core-server.js +75 -18
- package/dist/core/create-core-server.js.map +1 -1
- package/dist/core/create-core-services.d.ts +12 -5
- package/dist/core/create-core-services.d.ts.map +1 -1
- package/dist/core/create-core-services.js +69 -36
- 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.d.ts +3 -1
- package/dist/modules/access/access.routes.d.ts.map +1 -1
- package/dist/modules/access/access.routes.js +22 -20
- 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/render-roles-yaml.d.ts +22 -0
- package/dist/modules/access/render-roles-yaml.d.ts.map +1 -0
- package/dist/modules/access/render-roles-yaml.js +56 -0
- package/dist/modules/access/render-roles-yaml.js.map +1 -0
- package/dist/modules/access/roles-admin.service.d.ts +33 -5
- package/dist/modules/access/roles-admin.service.d.ts.map +1 -1
- package/dist/modules/access/roles-admin.service.js +44 -19
- 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/settings/setup.routes.d.ts +10 -1
- package/dist/modules/settings/setup.routes.d.ts.map +1 -1
- package/dist/modules/settings/setup.routes.js +111 -6
- package/dist/modules/settings/setup.routes.js.map +1 -1
- 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/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/startup/kb-git.d.ts +23 -0
- package/dist/modules/workspace/startup/kb-git.d.ts.map +1 -0
- package/dist/modules/workspace/startup/kb-git.js +86 -0
- package/dist/modules/workspace/startup/kb-git.js.map +1 -0
- package/dist/modules/workspace/startup/kb-startup-runner.d.ts +74 -0
- package/dist/modules/workspace/startup/kb-startup-runner.d.ts.map +1 -0
- package/dist/modules/workspace/startup/kb-startup-runner.js +528 -0
- package/dist/modules/workspace/startup/kb-startup-runner.js.map +1 -0
- package/dist/modules/workspace/startup/on-server-start.d.ts +105 -0
- package/dist/modules/workspace/startup/on-server-start.d.ts.map +1 -0
- package/dist/modules/workspace/startup/on-server-start.js +21 -0
- package/dist/modules/workspace/startup/on-server-start.js.map +1 -0
- package/dist/modules/workspace/startup/steps/groups-to-plugins.step.d.ts +46 -0
- package/dist/modules/workspace/startup/steps/groups-to-plugins.step.d.ts.map +1 -0
- package/dist/modules/workspace/startup/steps/groups-to-plugins.step.js +492 -0
- package/dist/modules/workspace/startup/steps/groups-to-plugins.step.js.map +1 -0
- package/dist/modules/workspace/startup/steps/roles-yaml.step.d.ts +23 -0
- package/dist/modules/workspace/startup/steps/roles-yaml.step.d.ts.map +1 -0
- package/dist/modules/workspace/startup/steps/roles-yaml.step.js +69 -0
- package/dist/modules/workspace/startup/steps/roles-yaml.step.js.map +1 -0
- package/dist/modules/workspace/startup/steps/seed-tree.d.ts +17 -0
- package/dist/modules/workspace/startup/steps/seed-tree.d.ts.map +1 -0
- package/dist/modules/workspace/startup/steps/seed-tree.js +109 -0
- package/dist/modules/workspace/startup/steps/seed-tree.js.map +1 -0
- package/dist/modules/workspace/startup/steps/template-files.step.d.ts +103 -0
- package/dist/modules/workspace/startup/steps/template-files.step.d.ts.map +1 -0
- package/dist/modules/workspace/startup/steps/template-files.step.js +337 -0
- package/dist/modules/workspace/startup/steps/template-files.step.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 +0 -9
- package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.service.js +2 -30
- 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/kb-template/gitignore.template +16 -0
- 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 +113 -106
- package/src/core/create-core-server.ts +85 -20
- package/src/core/create-core-services.ts +83 -42
- 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 +36 -2
- 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 +23 -20
- package/src/modules/access/creator-access.ts +5 -5
- package/src/modules/access/render-roles-yaml.ts +65 -0
- package/src/modules/access/roles-admin.service.ts +45 -16
- 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/settings/__tests__/setup.routes.test.ts +124 -3
- package/src/modules/settings/setup.routes.ts +112 -5
- 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__/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/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/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 +27 -0
- package/src/modules/workspace/bevel-ignore.ts +66 -66
- package/src/modules/workspace/session-sink.ts +25 -25
- package/src/modules/workspace/startup/__tests__/kb-startup-runner.test.ts +495 -0
- package/src/modules/workspace/startup/kb-git.ts +94 -0
- package/src/modules/workspace/startup/kb-startup-runner.ts +597 -0
- package/src/modules/workspace/startup/on-server-start.ts +97 -0
- package/src/modules/workspace/startup/steps/__tests__/steps.test.ts +636 -0
- package/src/modules/workspace/startup/steps/groups-to-plugins.step.ts +561 -0
- package/src/modules/workspace/startup/steps/roles-yaml.step.ts +71 -0
- package/src/modules/workspace/startup/steps/seed-tree.ts +115 -0
- package/src/modules/workspace/startup/steps/template-files.step.ts +360 -0
- package/src/modules/workspace/workspace.routes.ts +3 -3
- package/src/modules/workspace/workspace.service.ts +2 -35
- 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/modules/workspace/__tests__/kb-seed.service.test.ts +0 -444
- package/src/modules/workspace/kb-seed.interface.ts +0 -36
- package/src/modules/workspace/kb-seed.service.ts +0 -555
- package/src/shared/__tests__/kb-layout.group.test.ts +0 -45
- /package/kb-template/{Groups → Plugins}/.gitkeep +0 -0
|
@@ -11,35 +11,35 @@ import type { IAccessControl } from '../access/access-control.interface.js';
|
|
|
11
11
|
import { spliceGrant } from '../access/access-splice.js';
|
|
12
12
|
import { WorkflowDomainError } from '../workflow/workflow.errors.js';
|
|
13
13
|
import type { WorkspaceService } from '../workspace/workspace.service.js';
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
14
|
+
import { pluginsWorkspaceId } from './plugins.service.js';
|
|
15
|
+
import { PluginProvisionError, type PluginProvisionService } from './plugin-provision.service.js';
|
|
16
16
|
import type { JoinRequestsService } from './join-requests.service.js';
|
|
17
17
|
import type {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
} from './
|
|
18
|
+
PluginCatalogEntry,
|
|
19
|
+
PluginSummary,
|
|
20
|
+
IPluginIndexService,
|
|
21
|
+
} from './plugins.contract.js';
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
|
-
* Browser-facing (JWT)
|
|
24
|
+
* Browser-facing (JWT) plugin routes, mounted behind `authMiddleware`:
|
|
25
25
|
*
|
|
26
|
-
* GET /api/
|
|
27
|
-
* DELETE /api/
|
|
28
|
-
* POST /api/
|
|
29
|
-
* GET /api/
|
|
30
|
-
* POST /api/
|
|
26
|
+
* GET /api/plugins → { plugins: PluginSummary[] }
|
|
27
|
+
* DELETE /api/plugins/:name → { ok } (owners)
|
|
28
|
+
* POST /api/plugins/:name/join-request → { ok, number } (opens a CR)
|
|
29
|
+
* GET /api/plugins/:name/join-requests → { requests } (managers)
|
|
30
|
+
* POST /api/plugins/:name/join-requests/:n/reconcile → { closed }
|
|
31
31
|
*
|
|
32
|
-
* Enumeration is three verdicts per (caller,
|
|
32
|
+
* Enumeration is three verdicts per (caller, plugin), all of them ordinary
|
|
33
33
|
* access resolution — no special cases, no side tables:
|
|
34
34
|
*
|
|
35
|
-
* member canRead on the
|
|
35
|
+
* member canRead on the plugin FOLDER
|
|
36
36
|
* manager canWrite on the folder's access.md (admin-rescued)
|
|
37
37
|
* discoverable canRead on the access.md FILE — in the body-governed format
|
|
38
38
|
* its own `read: everyone` frontmatter grants this
|
|
39
39
|
*
|
|
40
|
-
* All three false ⇒ the
|
|
40
|
+
* All three false ⇒ the plugin is absent from the response entirely.
|
|
41
41
|
*
|
|
42
|
-
* A join request is a plain change request whose branch edits the
|
|
42
|
+
* A join request is a plain change request whose branch edits the plugin's
|
|
43
43
|
* `access.md`. Managers do NOT merge it: they read its individual proposals
|
|
44
44
|
* (see `join-proposals.ts`), grant the ones they accept through the ordinary
|
|
45
45
|
* access path, and the request retires itself once its rules are a subset of
|
|
@@ -52,13 +52,13 @@ import type {
|
|
|
52
52
|
* middleware change can never silently un-gate one. Nothing here is reachable
|
|
53
53
|
* with an agent connection key or a manual-auth bearer.
|
|
54
54
|
*/
|
|
55
|
-
export function
|
|
56
|
-
|
|
55
|
+
export function createPluginsRoutes(
|
|
56
|
+
pluginIndex: IPluginIndexService,
|
|
57
57
|
accessControl: IAccessControl,
|
|
58
58
|
workflow: IWorkflowService,
|
|
59
59
|
workspaceService: WorkspaceService,
|
|
60
60
|
joinRequests: JoinRequestsService,
|
|
61
|
-
provision:
|
|
61
|
+
provision: PluginProvisionService,
|
|
62
62
|
kbDirName: string,
|
|
63
63
|
resolveUser: (req: express.Request) => Promise<AuthUser | null>,
|
|
64
64
|
): express.Router {
|
|
@@ -69,27 +69,27 @@ export function createGroupsRoutes(
|
|
|
69
69
|
/** The FILE probe for discovery/management — the folder's access.md. */
|
|
70
70
|
const accessMdOf = (folder: string) => `${folder}/access.md`;
|
|
71
71
|
|
|
72
|
-
const probesFor = (
|
|
73
|
-
...new Set(
|
|
72
|
+
const probesFor = (plugins: PluginCatalogEntry[]): string[] => [
|
|
73
|
+
...new Set(plugins.flatMap((g) => g.folders.flatMap((f) => [memberProbe(f), accessMdOf(f)]))),
|
|
74
74
|
];
|
|
75
75
|
|
|
76
|
-
/** The caller's open join CR for `
|
|
77
|
-
const openJoinCr = (mine: ChangeRequest[], email: string,
|
|
78
|
-
mine.find((cr) => cr.state === 'open' && cr.branch === joinBranchFor(email,
|
|
76
|
+
/** The caller's open join CR for `plugin`, or null. */
|
|
77
|
+
const openJoinCr = (mine: ChangeRequest[], email: string, plugin: string): ChangeRequest | null =>
|
|
78
|
+
mine.find((cr) => cr.state === 'open' && cr.branch === joinBranchFor(email, plugin)) ?? null;
|
|
79
79
|
|
|
80
|
-
router.get('/
|
|
80
|
+
router.get('/plugins', async (req, res) => {
|
|
81
81
|
const email = req.userEmail;
|
|
82
82
|
if (!email) {
|
|
83
83
|
res.status(401).json({ error: 'Unauthenticated' });
|
|
84
84
|
return;
|
|
85
85
|
}
|
|
86
86
|
try {
|
|
87
|
-
const catalog = await
|
|
87
|
+
const catalog = await pluginIndex.catalog();
|
|
88
88
|
if (catalog.length === 0) {
|
|
89
|
-
res.json({
|
|
89
|
+
res.json({ plugins: [] });
|
|
90
90
|
return;
|
|
91
91
|
}
|
|
92
|
-
const wsId =
|
|
92
|
+
const wsId = pluginsWorkspaceId();
|
|
93
93
|
const probes = probesFor(catalog);
|
|
94
94
|
const [readable, writable, owned] = await Promise.all([
|
|
95
95
|
accessControl.canReadBatch(wsId, email, probes),
|
|
@@ -97,22 +97,22 @@ export function createGroupsRoutes(
|
|
|
97
97
|
accessControl.canOwnerBatch(wsId, email, probes),
|
|
98
98
|
]);
|
|
99
99
|
// Fail closed on every verdict: a path missing from the map is denied.
|
|
100
|
-
const any = (map: Map<string, boolean>, g:
|
|
100
|
+
const any = (map: Map<string, boolean>, g: PluginCatalogEntry, probe: (f: string) => string) =>
|
|
101
101
|
g.folders.some((f) => map.get(probe(f)) === true);
|
|
102
102
|
|
|
103
103
|
// The caller's own open join CRs — drives `hasRequested`. A CR-listing
|
|
104
|
-
// hiccup must not take
|
|
104
|
+
// hiccup must not take plugin enumeration down: degrade to "nothing
|
|
105
105
|
// requested" and let the next load repair it.
|
|
106
106
|
let mine: ChangeRequest[] = [];
|
|
107
107
|
try {
|
|
108
108
|
mine = await workflow.listChangeRequestsAuthoredBy(email);
|
|
109
109
|
} catch (err) {
|
|
110
110
|
console.warn(
|
|
111
|
-
`[
|
|
111
|
+
`[plugins] join-request lookup failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
112
112
|
);
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
const
|
|
115
|
+
const plugins: PluginSummary[] = [];
|
|
116
116
|
for (const g of catalog) {
|
|
117
117
|
const member = any(readable, g, memberProbe);
|
|
118
118
|
const manager = any(writable, g, accessMdOf);
|
|
@@ -123,7 +123,7 @@ export function createGroupsRoutes(
|
|
|
123
123
|
const discoverable = member || any(readable, g, accessMdOf);
|
|
124
124
|
if (!member && !manager && !discoverable) continue; // absent — fail closed
|
|
125
125
|
const joinCr = member ? null : openJoinCr(mine, email, g.name);
|
|
126
|
-
|
|
126
|
+
plugins.push({
|
|
127
127
|
name: g.name,
|
|
128
128
|
folders: g.folders,
|
|
129
129
|
canRead: member,
|
|
@@ -138,10 +138,10 @@ export function createGroupsRoutes(
|
|
|
138
138
|
requestNumber: joinCr?.number ?? null,
|
|
139
139
|
});
|
|
140
140
|
}
|
|
141
|
-
res.json({
|
|
141
|
+
res.json({ plugins });
|
|
142
142
|
} catch (err) {
|
|
143
|
-
console.error('[
|
|
144
|
-
res.status(500).json({ error: 'Failed to list
|
|
143
|
+
console.error('[plugins] failed to list plugins:', err);
|
|
144
|
+
res.status(500).json({ error: 'Failed to list plugins' });
|
|
145
145
|
}
|
|
146
146
|
});
|
|
147
147
|
|
|
@@ -155,13 +155,13 @@ export function createGroupsRoutes(
|
|
|
155
155
|
* approver who can write the touched access.md.
|
|
156
156
|
*/
|
|
157
157
|
/**
|
|
158
|
-
* Create a
|
|
158
|
+
* Create a plugin — the ONE door through which `Plugins/<name>/` folders come
|
|
159
159
|
* to exist. Any authenticated user may create one; that is the product
|
|
160
|
-
* model (making a
|
|
160
|
+
* model (making a plugin makes you the one who runs it), and the seeded
|
|
161
161
|
* access.md immediately fences the new folder off from everyone else. See
|
|
162
|
-
* `
|
|
162
|
+
* `PluginProvisionService` for why this is an endpoint and not a write path.
|
|
163
163
|
*/
|
|
164
|
-
router.post('/
|
|
164
|
+
router.post('/plugins', async (req, res) => {
|
|
165
165
|
const user = await resolveUser(req);
|
|
166
166
|
if (!user) {
|
|
167
167
|
res.status(401).json({ error: 'Unauthenticated' });
|
|
@@ -175,50 +175,50 @@ export function createGroupsRoutes(
|
|
|
175
175
|
return;
|
|
176
176
|
}
|
|
177
177
|
try {
|
|
178
|
-
const result = await provision.
|
|
178
|
+
const result = await provision.createPlugin(user, name);
|
|
179
179
|
res.status(201).json(result);
|
|
180
180
|
} catch (err) {
|
|
181
|
-
if (err instanceof
|
|
181
|
+
if (err instanceof PluginProvisionError) {
|
|
182
182
|
res.status(err.status).json({ error: err.message });
|
|
183
183
|
return;
|
|
184
184
|
}
|
|
185
|
-
console.error('[
|
|
186
|
-
res.status(500).json({ error: 'Failed to create the
|
|
185
|
+
console.error('[plugins] create failed:', err);
|
|
186
|
+
res.status(500).json({ error: 'Failed to create the plugin' });
|
|
187
187
|
}
|
|
188
188
|
});
|
|
189
189
|
|
|
190
190
|
/**
|
|
191
|
-
* Ensure the caller's personal folder (`
|
|
191
|
+
* Ensure the caller's personal folder (`Plugins/personal-<id>/`) exists —
|
|
192
192
|
* idempotent; the UI calls it lazily right before the first personal-skill
|
|
193
193
|
* write. Private by construction: its access.md names only the caller.
|
|
194
194
|
*/
|
|
195
|
-
router.post('/
|
|
195
|
+
router.post('/plugins/personal', async (req, res) => {
|
|
196
196
|
const user = await resolveUser(req);
|
|
197
197
|
if (!user) {
|
|
198
198
|
res.status(401).json({ error: 'Unauthenticated' });
|
|
199
199
|
return;
|
|
200
200
|
}
|
|
201
201
|
try {
|
|
202
|
-
res.json(await provision.
|
|
202
|
+
res.json(await provision.ensurePersonalPlugin(user));
|
|
203
203
|
} catch (err) {
|
|
204
|
-
console.error('[
|
|
204
|
+
console.error('[plugins] personal-folder ensure failed:', err);
|
|
205
205
|
res.status(500).json({ error: 'Failed to prepare your personal folder' });
|
|
206
206
|
}
|
|
207
207
|
});
|
|
208
208
|
|
|
209
209
|
/**
|
|
210
|
-
* Delete a
|
|
210
|
+
* Delete a plugin — the OWNER's verb, and only theirs. Creating a plugin
|
|
211
211
|
* makes you the one who runs it; deleting it is the other end of that same
|
|
212
212
|
* promise, so the gate is the `owner` verdict on the folder (owner-lists
|
|
213
213
|
* only, no admin rescue) — a manager who merely writes the access.md, and
|
|
214
214
|
* an admin rescued into managing it, do not get it.
|
|
215
215
|
*
|
|
216
|
-
* Fail-closed like every other
|
|
216
|
+
* Fail-closed like every other plugin surface: an unknown plugin and a plugin
|
|
217
217
|
* the caller does not own answer IDENTICALLY, so probing the endpoint can
|
|
218
218
|
* confirm nothing about what exists. The mechanism (park, one commit,
|
|
219
|
-
* rollback on refusal) lives in `
|
|
219
|
+
* rollback on refusal) lives in `PluginProvisionService.deletePlugin`.
|
|
220
220
|
*/
|
|
221
|
-
router.delete('/
|
|
221
|
+
router.delete('/plugins/:name', async (req, res) => {
|
|
222
222
|
const email = req.userEmail;
|
|
223
223
|
if (!email) {
|
|
224
224
|
res.status(401).json({ error: 'Unauthenticated' });
|
|
@@ -230,32 +230,32 @@ export function createGroupsRoutes(
|
|
|
230
230
|
res.status(401).json({ error: 'Unauthenticated' });
|
|
231
231
|
return;
|
|
232
232
|
}
|
|
233
|
-
// Case-sensitive, like `
|
|
234
|
-
const
|
|
235
|
-
const wsId =
|
|
236
|
-
const ownerVerdicts =
|
|
233
|
+
// Case-sensitive, like `pluginOfPath` — the plugin name IS the folder name.
|
|
234
|
+
const plugin = (await pluginIndex.catalog()).find((g) => g.name === req.params.name);
|
|
235
|
+
const wsId = pluginsWorkspaceId();
|
|
236
|
+
const ownerVerdicts = plugin
|
|
237
237
|
? await Promise.all(
|
|
238
|
-
|
|
238
|
+
plugin.folders.map((f) => accessControl.canOwner(wsId, email, memberProbe(f))),
|
|
239
239
|
)
|
|
240
240
|
: [];
|
|
241
|
-
if (!
|
|
242
|
-
res.status(404).json({ error: 'Unknown
|
|
241
|
+
if (!plugin || !ownerVerdicts.some((v) => v === true)) {
|
|
242
|
+
res.status(404).json({ error: 'Unknown plugin', kind: 'unknown-plugin' });
|
|
243
243
|
return;
|
|
244
244
|
}
|
|
245
|
-
await provision.
|
|
246
|
-
|
|
245
|
+
await provision.deletePlugin(user, plugin.name);
|
|
246
|
+
pluginIndex.invalidate();
|
|
247
247
|
res.json({ ok: true });
|
|
248
248
|
} catch (err) {
|
|
249
|
-
if (err instanceof
|
|
249
|
+
if (err instanceof PluginProvisionError) {
|
|
250
250
|
res.status(err.status).json({ error: err.message });
|
|
251
251
|
return;
|
|
252
252
|
}
|
|
253
|
-
console.error('[
|
|
254
|
-
res.status(500).json({ error: 'Failed to delete the
|
|
253
|
+
console.error('[plugins] delete failed:', err);
|
|
254
|
+
res.status(500).json({ error: 'Failed to delete the plugin' });
|
|
255
255
|
}
|
|
256
256
|
});
|
|
257
257
|
|
|
258
|
-
router.post('/
|
|
258
|
+
router.post('/plugins/:name/join-request', async (req, res) => {
|
|
259
259
|
const email = req.userEmail;
|
|
260
260
|
if (!email) {
|
|
261
261
|
res.status(401).json({ error: 'Unauthenticated' });
|
|
@@ -267,39 +267,39 @@ export function createGroupsRoutes(
|
|
|
267
267
|
res.status(401).json({ error: 'Unauthenticated' });
|
|
268
268
|
return;
|
|
269
269
|
}
|
|
270
|
-
const catalog = await
|
|
271
|
-
// Case-sensitive, like `
|
|
272
|
-
const
|
|
273
|
-
const wsId =
|
|
274
|
-
// Same ANY-folder shape `GET /
|
|
270
|
+
const catalog = await pluginIndex.catalog();
|
|
271
|
+
// Case-sensitive, like `pluginOfPath` — the plugin name IS the folder name.
|
|
272
|
+
const plugin = catalog.find((g) => g.name === req.params.name);
|
|
273
|
+
const wsId = pluginsWorkspaceId();
|
|
274
|
+
// Same ANY-folder shape `GET /plugins` resolves with, so a plugin can
|
|
275
275
|
// never be listed as discoverable there and rejected as unknown here.
|
|
276
|
-
const verdicts =
|
|
276
|
+
const verdicts = plugin
|
|
277
277
|
? await accessControl.canReadBatch(
|
|
278
278
|
wsId,
|
|
279
279
|
email,
|
|
280
|
-
|
|
280
|
+
plugin.folders.flatMap((f) => [memberProbe(f), accessMdOf(f)]),
|
|
281
281
|
)
|
|
282
282
|
: new Map<string, boolean>();
|
|
283
283
|
const any = (probe: (f: string) => string) =>
|
|
284
|
-
|
|
285
|
-
// Discovery gate, fail-closed: an unknown
|
|
284
|
+
plugin?.folders.some((f) => verdicts.get(probe(f)) === true) ?? false;
|
|
285
|
+
// Discovery gate, fail-closed: an unknown plugin and a plugin the caller
|
|
286
286
|
// cannot discover answer IDENTICALLY, so probing can't confirm existence.
|
|
287
|
-
if (!
|
|
288
|
-
res.status(404).json({ error: 'Unknown
|
|
287
|
+
if (!plugin || !any(accessMdOf)) {
|
|
288
|
+
res.status(404).json({ error: 'Unknown plugin', kind: 'unknown-plugin' });
|
|
289
289
|
return;
|
|
290
290
|
}
|
|
291
291
|
if (any(memberProbe)) {
|
|
292
292
|
// Access landed between page load and click — reload, don't ask.
|
|
293
|
-
res.status(409).json({ error: 'You can already read this
|
|
293
|
+
res.status(409).json({ error: 'You can already read this plugin', kind: 'already-readable' });
|
|
294
294
|
return;
|
|
295
295
|
}
|
|
296
|
-
// The grant is written to the
|
|
296
|
+
// The grant is written to the plugin's primary folder — the one the
|
|
297
297
|
// summary's `folders[0]` names and the banner's touched-path check
|
|
298
298
|
// expects.
|
|
299
|
-
const folder =
|
|
299
|
+
const folder = plugin.folders[0];
|
|
300
300
|
|
|
301
|
-
const branch = joinBranchFor(email,
|
|
302
|
-
const existing = openJoinCr(await workflow.listChangeRequestsAuthoredBy(email), email,
|
|
301
|
+
const branch = joinBranchFor(email, plugin.name);
|
|
302
|
+
const existing = openJoinCr(await workflow.listChangeRequestsAuthoredBy(email), email, plugin.name);
|
|
303
303
|
if (existing) {
|
|
304
304
|
res.json({ ok: true, number: existing.number });
|
|
305
305
|
return;
|
|
@@ -308,7 +308,7 @@ export function createGroupsRoutes(
|
|
|
308
308
|
// A leftover branch from a rejected/withdrawn request is reused — the
|
|
309
309
|
// grant commit is already on it and the splice below no-ops.
|
|
310
310
|
try {
|
|
311
|
-
await workflow.createBranch(
|
|
311
|
+
await workflow.createBranch(pluginsWorkspaceId(), branch, DEFAULT_BRANCH);
|
|
312
312
|
} catch {
|
|
313
313
|
// exists (or raced) — proceed against it
|
|
314
314
|
}
|
|
@@ -323,14 +323,14 @@ export function createGroupsRoutes(
|
|
|
323
323
|
);
|
|
324
324
|
if (spliced.changed) {
|
|
325
325
|
await workspaceService.writeFile(ws.id, accessPath, spliced.text);
|
|
326
|
-
await workflow.commitChanges(ws.id, user, `Request access to ${
|
|
326
|
+
await workflow.commitChanges(ws.id, user, `Request access to ${plugin.name}`);
|
|
327
327
|
}
|
|
328
328
|
const detail = await workflow.openChangeRequest(ws.id, user, {
|
|
329
329
|
sourceBranch: branch,
|
|
330
330
|
targetBranch: DEFAULT_BRANCH,
|
|
331
|
-
title: `Join request: ${
|
|
331
|
+
title: `Join request: ${plugin.name}`,
|
|
332
332
|
description:
|
|
333
|
-
`${user.name} asked to join ${
|
|
333
|
+
`${user.name} asked to join ${plugin.name}. A manager of the plugin accepts by ` +
|
|
334
334
|
`granting the access this branch proposes; the request closes itself once ` +
|
|
335
335
|
`every proposal has landed.`,
|
|
336
336
|
});
|
|
@@ -340,41 +340,41 @@ export function createGroupsRoutes(
|
|
|
340
340
|
res.status(err.status).json({ error: err.message, ...(err.payload ?? {}) });
|
|
341
341
|
return;
|
|
342
342
|
}
|
|
343
|
-
console.error('[
|
|
343
|
+
console.error('[plugins] failed to open a join request:', err);
|
|
344
344
|
res.status(500).json({ error: 'Failed to request access' });
|
|
345
345
|
}
|
|
346
346
|
});
|
|
347
347
|
|
|
348
348
|
/**
|
|
349
349
|
* Resolve the caller as a MANAGER of `name` — the only role that may see or
|
|
350
|
-
* settle its join requests. Returns the
|
|
350
|
+
* settle its join requests. Returns the plugin's primary folder, or null
|
|
351
351
|
* after answering the request.
|
|
352
352
|
*
|
|
353
353
|
* A non-manager gets an empty list rather than a 403 (the frontend asks
|
|
354
|
-
* unconditionally, exactly as it does for every other
|
|
355
|
-
* an unknown
|
|
354
|
+
* unconditionally, exactly as it does for every other plugin surface), and
|
|
355
|
+
* an unknown plugin is indistinguishable from an unmanaged one.
|
|
356
356
|
*/
|
|
357
357
|
async function requireManager(
|
|
358
358
|
req: express.Request,
|
|
359
359
|
res: express.Response,
|
|
360
360
|
onDenied: () => void,
|
|
361
|
-
): Promise<{
|
|
361
|
+
): Promise<{ plugin: PluginCatalogEntry; folder: string; user: AuthUser } | null> {
|
|
362
362
|
const email = req.userEmail;
|
|
363
363
|
if (!email) {
|
|
364
364
|
res.status(401).json({ error: 'Unauthenticated' });
|
|
365
365
|
return null;
|
|
366
366
|
}
|
|
367
|
-
const
|
|
368
|
-
if (!
|
|
367
|
+
const plugin = (await pluginIndex.catalog()).find((g) => g.name === req.params.name);
|
|
368
|
+
if (!plugin) {
|
|
369
369
|
onDenied();
|
|
370
370
|
return null;
|
|
371
371
|
}
|
|
372
372
|
const writable = await accessControl.canWriteBatch(
|
|
373
|
-
|
|
373
|
+
pluginsWorkspaceId(),
|
|
374
374
|
email,
|
|
375
|
-
|
|
375
|
+
plugin.folders.map(accessMdOf),
|
|
376
376
|
);
|
|
377
|
-
if (!
|
|
377
|
+
if (!plugin.folders.some((f) => writable.get(accessMdOf(f)) === true)) {
|
|
378
378
|
onDenied();
|
|
379
379
|
return null;
|
|
380
380
|
}
|
|
@@ -383,19 +383,19 @@ export function createGroupsRoutes(
|
|
|
383
383
|
res.status(401).json({ error: 'Unauthenticated' });
|
|
384
384
|
return null;
|
|
385
385
|
}
|
|
386
|
-
return {
|
|
386
|
+
return { plugin, folder: plugin.folders[0], user };
|
|
387
387
|
}
|
|
388
388
|
|
|
389
|
-
router.get('/
|
|
389
|
+
router.get('/plugins/:name/join-requests', async (req, res) => {
|
|
390
390
|
try {
|
|
391
391
|
const ctx = await requireManager(req, res, () => res.json({ requests: [] }));
|
|
392
392
|
if (!ctx) return;
|
|
393
393
|
const crs = await workflow.listChangeRequests();
|
|
394
394
|
res.json({
|
|
395
|
-
requests: await joinRequests.list(ctx.
|
|
395
|
+
requests: await joinRequests.list(ctx.plugin.name, ctx.folder, crs, ctx.user),
|
|
396
396
|
});
|
|
397
397
|
} catch (err) {
|
|
398
|
-
console.error('[
|
|
398
|
+
console.error('[plugins] failed to list join requests:', err);
|
|
399
399
|
res.status(500).json({ error: 'Failed to list join requests' });
|
|
400
400
|
}
|
|
401
401
|
});
|
|
@@ -406,7 +406,7 @@ export function createGroupsRoutes(
|
|
|
406
406
|
* round-trip; the listing does the same thing lazily, so skipping this (a
|
|
407
407
|
* dropped response, a closed tab) only delays it.
|
|
408
408
|
*/
|
|
409
|
-
router.post('/
|
|
409
|
+
router.post('/plugins/:name/join-requests/:number/reconcile', async (req, res) => {
|
|
410
410
|
try {
|
|
411
411
|
const ctx = await requireManager(req, res, () =>
|
|
412
412
|
res.status(404).json({ error: 'Not found' }),
|
|
@@ -422,9 +422,9 @@ export function createGroupsRoutes(
|
|
|
422
422
|
res.status(404).json({ error: 'Not found' });
|
|
423
423
|
return;
|
|
424
424
|
}
|
|
425
|
-
res.json({ closed: await joinRequests.reconcile(ctx.
|
|
425
|
+
res.json({ closed: await joinRequests.reconcile(ctx.plugin.name, ctx.folder, cr, ctx.user) });
|
|
426
426
|
} catch (err) {
|
|
427
|
-
console.error('[
|
|
427
|
+
console.error('[plugins] failed to reconcile a join request:', err);
|
|
428
428
|
res.status(500).json({ error: 'Failed to update the request' });
|
|
429
429
|
}
|
|
430
430
|
});
|