@bevel-software/platform-core-backend 0.4.1 → 0.5.1
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/THIRD-PARTY-NOTICES.md +6118 -0
- package/dist/core/core-ports.d.ts +15 -0
- 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 +40 -6
- package/dist/core/create-core-server.js.map +1 -1
- package/dist/core/create-core-services.d.ts +17 -1
- package/dist/core/create-core-services.d.ts.map +1 -1
- package/dist/core/create-core-services.js +153 -29
- package/dist/core/create-core-services.js.map +1 -1
- package/dist/core-config.d.ts +70 -21
- package/dist/core-config.d.ts.map +1 -1
- package/dist/core-config.js +134 -58
- package/dist/core-config.js.map +1 -1
- package/dist/modules/access/access-control.interface.d.ts +6 -0
- package/dist/modules/access/access-control.interface.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.d.ts +74 -5
- package/dist/modules/access/access-control.service.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.js +116 -15
- package/dist/modules/access/access-control.service.js.map +1 -1
- package/dist/modules/access/access-declarations.d.ts +93 -0
- package/dist/modules/access/access-declarations.d.ts.map +1 -0
- package/dist/modules/access/access-declarations.js +168 -0
- package/dist/modules/access/access-declarations.js.map +1 -0
- package/dist/modules/access/access-mutation.service.js +4 -4
- package/dist/modules/access/access-mutation.service.js.map +1 -1
- package/dist/modules/access/access-splice.d.ts +16 -1
- package/dist/modules/access/access-splice.d.ts.map +1 -1
- package/dist/modules/access/access-splice.js +40 -21
- package/dist/modules/access/access-splice.js.map +1 -1
- package/dist/modules/access/access.routes.d.ts.map +1 -1
- package/dist/modules/access/access.routes.js +51 -0
- package/dist/modules/access/access.routes.js.map +1 -1
- package/dist/modules/access/creator-access.d.ts +8 -0
- package/dist/modules/access/creator-access.d.ts.map +1 -1
- package/dist/modules/access/creator-access.js +21 -2
- package/dist/modules/access/creator-access.js.map +1 -1
- package/dist/modules/admin/admin-access.service.d.ts +1 -1
- package/dist/modules/admin/admin-access.service.d.ts.map +1 -1
- package/dist/modules/admin/admin-access.service.js +3 -2
- package/dist/modules/admin/admin-access.service.js.map +1 -1
- package/dist/modules/auth/auth.routes.d.ts.map +1 -1
- package/dist/modules/auth/auth.routes.js +50 -1
- package/dist/modules/auth/auth.routes.js.map +1 -1
- package/dist/modules/auth/auth.service.d.ts +39 -10
- package/dist/modules/auth/auth.service.d.ts.map +1 -1
- package/dist/modules/auth/auth.service.js +38 -16
- package/dist/modules/auth/auth.service.js.map +1 -1
- package/dist/modules/database/core-schema.d.ts +124 -0
- package/dist/modules/database/core-schema.d.ts.map +1 -1
- package/dist/modules/database/core-schema.js +45 -0
- package/dist/modules/database/core-schema.js.map +1 -1
- package/dist/modules/groups/group-provision.service.d.ts +130 -0
- package/dist/modules/groups/group-provision.service.d.ts.map +1 -0
- package/dist/modules/groups/group-provision.service.js +288 -0
- package/dist/modules/groups/group-provision.service.js.map +1 -0
- package/dist/modules/groups/groups.contract.d.ts +106 -0
- package/dist/modules/groups/groups.contract.d.ts.map +1 -0
- package/dist/modules/groups/groups.contract.js +36 -0
- package/dist/modules/groups/groups.contract.js.map +1 -0
- package/dist/modules/groups/groups.routes.d.ts +42 -0
- package/dist/modules/groups/groups.routes.d.ts.map +1 -0
- package/dist/modules/groups/groups.routes.js +379 -0
- package/dist/modules/groups/groups.routes.js.map +1 -0
- package/dist/modules/groups/groups.service.d.ts +60 -0
- package/dist/modules/groups/groups.service.d.ts.map +1 -0
- package/dist/modules/groups/groups.service.js +172 -0
- package/dist/modules/groups/groups.service.js.map +1 -0
- package/dist/modules/groups/index.d.ts +7 -0
- package/dist/modules/groups/index.d.ts.map +1 -0
- package/dist/modules/groups/index.js +6 -0
- package/dist/modules/groups/index.js.map +1 -0
- package/dist/modules/groups/join-proposals.d.ts +53 -0
- package/dist/modules/groups/join-proposals.d.ts.map +1 -0
- package/dist/modules/groups/join-proposals.js +67 -0
- package/dist/modules/groups/join-proposals.js.map +1 -0
- package/dist/modules/groups/join-requests.service.d.ts +81 -0
- package/dist/modules/groups/join-requests.service.d.ts.map +1 -0
- package/dist/modules/groups/join-requests.service.js +135 -0
- package/dist/modules/groups/join-requests.service.js.map +1 -0
- package/dist/modules/secrets-vault/secrets-vault.routes.d.ts +20 -0
- package/dist/modules/secrets-vault/secrets-vault.routes.d.ts.map +1 -1
- package/dist/modules/secrets-vault/secrets-vault.routes.js +59 -11
- package/dist/modules/secrets-vault/secrets-vault.routes.js.map +1 -1
- package/dist/modules/settings/deployment-settings.service.d.ts +129 -0
- package/dist/modules/settings/deployment-settings.service.d.ts.map +1 -0
- package/dist/modules/settings/deployment-settings.service.js +368 -0
- package/dist/modules/settings/deployment-settings.service.js.map +1 -0
- package/dist/modules/settings/setup.routes.d.ts +49 -0
- package/dist/modules/settings/setup.routes.d.ts.map +1 -0
- package/dist/modules/settings/setup.routes.js +292 -0
- package/dist/modules/settings/setup.routes.js.map +1 -0
- package/dist/modules/skills/index.d.ts +2 -1
- package/dist/modules/skills/index.d.ts.map +1 -1
- package/dist/modules/skills/index.js +1 -0
- package/dist/modules/skills/index.js.map +1 -1
- package/dist/modules/skills/pending-skills.service.d.ts +40 -0
- package/dist/modules/skills/pending-skills.service.d.ts.map +1 -0
- package/dist/modules/skills/pending-skills.service.js +147 -0
- package/dist/modules/skills/pending-skills.service.js.map +1 -0
- package/dist/modules/skills/skills.contract.d.ts +32 -3
- 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.routes.d.ts +3 -2
- package/dist/modules/skills/skills.routes.d.ts.map +1 -1
- package/dist/modules/skills/skills.routes.js +18 -1
- package/dist/modules/skills/skills.routes.js.map +1 -1
- package/dist/modules/skills/skills.service.d.ts +22 -0
- package/dist/modules/skills/skills.service.d.ts.map +1 -1
- package/dist/modules/skills/skills.service.js +24 -12
- package/dist/modules/skills/skills.service.js.map +1 -1
- package/dist/modules/tool-manuals/index.d.ts +1 -1
- package/dist/modules/tool-manuals/index.d.ts.map +1 -1
- package/dist/modules/tool-manuals/index.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.contract.d.ts +47 -2
- 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 +4 -0
- package/dist/modules/tool-manuals/tool-manuals.routes.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.routes.js +23 -0
- package/dist/modules/tool-manuals/tool-manuals.routes.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.service.d.ts +3 -1
- package/dist/modules/tool-manuals/tool-manuals.service.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.service.js +84 -17
- package/dist/modules/tool-manuals/tool-manuals.service.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.tools.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.tools.js +6 -2
- package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
- package/dist/modules/workflow/agent-tools/workflow.tools.d.ts.map +1 -1
- package/dist/modules/workflow/agent-tools/workflow.tools.js +4 -2
- package/dist/modules/workflow/agent-tools/workflow.tools.js.map +1 -1
- package/dist/modules/workflow/event-bus.d.ts +11 -0
- package/dist/modules/workflow/event-bus.d.ts.map +1 -1
- package/dist/modules/workflow/event-bus.js +22 -0
- package/dist/modules/workflow/event-bus.js.map +1 -1
- package/dist/modules/workflow/file-lock.service.d.ts +9 -0
- package/dist/modules/workflow/file-lock.service.d.ts.map +1 -1
- package/dist/modules/workflow/file-lock.service.js +16 -0
- package/dist/modules/workflow/file-lock.service.js.map +1 -1
- package/dist/modules/workflow/git/branch-name.d.ts +1 -1
- package/dist/modules/workflow/git/branch-name.d.ts.map +1 -1
- package/dist/modules/workflow/git/branch-name.js +1 -1
- package/dist/modules/workflow/git/branch-name.js.map +1 -1
- package/dist/modules/workflow/git/git.service.d.ts +31 -2
- package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
- package/dist/modules/workflow/git/git.service.js +144 -52
- package/dist/modules/workflow/git/git.service.js.map +1 -1
- package/dist/modules/workflow/git/pull-request.service.d.ts +12 -6
- package/dist/modules/workflow/git/pull-request.service.d.ts.map +1 -1
- package/dist/modules/workflow/git/pull-request.service.js +19 -9
- package/dist/modules/workflow/git/pull-request.service.js.map +1 -1
- package/dist/modules/workflow/review-workflow/review-workflow.service.d.ts.map +1 -1
- package/dist/modules/workflow/review-workflow/review-workflow.service.js +12 -3
- package/dist/modules/workflow/review-workflow/review-workflow.service.js.map +1 -1
- package/dist/modules/workflow/workflow.routes.d.ts.map +1 -1
- package/dist/modules/workflow/workflow.routes.js +95 -14
- package/dist/modules/workflow/workflow.routes.js.map +1 -1
- package/dist/modules/workflow/workflow.service.d.ts +92 -3
- package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
- package/dist/modules/workflow/workflow.service.js +381 -108
- package/dist/modules/workflow/workflow.service.js.map +1 -1
- package/dist/modules/workspace/bevel-ignore.d.ts +5 -0
- package/dist/modules/workspace/bevel-ignore.d.ts.map +1 -1
- package/dist/modules/workspace/bevel-ignore.js +5 -1
- package/dist/modules/workspace/bevel-ignore.js.map +1 -1
- package/dist/modules/workspace/kb-seed.interface.d.ts +1 -1
- package/dist/modules/workspace/kb-seed.service.d.ts +60 -5
- package/dist/modules/workspace/kb-seed.service.d.ts.map +1 -1
- package/dist/modules/workspace/kb-seed.service.js +228 -51
- package/dist/modules/workspace/kb-seed.service.js.map +1 -1
- package/dist/modules/workspace/workspace.routes.d.ts +2 -1
- package/dist/modules/workspace/workspace.routes.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.routes.js +51 -18
- package/dist/modules/workspace/workspace.routes.js.map +1 -1
- package/dist/modules/workspace/workspace.service.d.ts +16 -4
- package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.service.js +36 -9
- package/dist/modules/workspace/workspace.service.js.map +1 -1
- package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.tools.js +8 -3
- package/dist/modules/workspace/workspace.tools.js.map +1 -1
- package/dist/shared/kb-layout.test.js +2 -2
- package/dist/test-setup.d.ts +2 -0
- package/dist/test-setup.d.ts.map +1 -0
- package/dist/test-setup.js +18 -0
- package/dist/test-setup.js.map +1 -0
- package/kb-template/.bevelignore +6 -2
- package/kb-template/AGENTS.md +272 -0
- package/kb-template/KnowledgeBase/How to get started.md +65 -0
- package/kb-template/access.md +36 -36
- package/migrations/0002_user_onboarding_done.sql +1 -0
- package/migrations/0003_deployment_settings.sql +9 -0
- package/migrations/meta/0001_snapshot.json +1486 -0
- package/migrations/meta/0002_snapshot.json +1493 -0
- package/migrations/meta/0003_snapshot.json +1552 -0
- package/migrations/meta/_journal.json +15 -1
- package/package.json +5 -4
- package/src/__tests__/branch-model.test.ts +79 -0
- package/src/__tests__/core-config.admin.test.ts +114 -0
- package/src/__tests__/database-url.test.ts +70 -0
- package/src/core/core-ports.ts +15 -0
- package/src/core/create-core-server.ts +55 -6
- package/src/core/create-core-services.ts +201 -36
- package/src/core-config.ts +139 -59
- package/src/modules/access/__tests__/access-declarations.test.ts +195 -0
- package/src/modules/access/__tests__/access-md-format.test.ts +191 -0
- package/src/modules/access/__tests__/access-splice.test.ts +21 -0
- package/src/modules/access/__tests__/access.routes.overrides.test.ts +214 -0
- package/src/modules/access/access-control.interface.ts +11 -0
- package/src/modules/access/access-control.service.ts +113 -15
- package/src/modules/access/access-declarations.ts +251 -0
- package/src/modules/access/access-mutation.service.ts +4 -4
- package/src/modules/access/access-splice.ts +64 -21
- package/src/modules/access/access.routes.ts +61 -0
- package/src/modules/access/creator-access.ts +22 -2
- package/src/modules/admin/admin-access.service.ts +4 -3
- package/src/modules/auth/__tests__/auth.service.test.ts +57 -0
- package/src/modules/auth/__tests__/onboarding-done.route.test.ts +147 -0
- package/src/modules/auth/auth.routes.ts +50 -1
- package/src/modules/auth/auth.service.ts +55 -17
- package/src/modules/database/core-schema.ts +46 -0
- package/src/modules/groups/__tests__/group-index.service.test.ts +225 -0
- package/src/modules/groups/__tests__/group-provision.service.test.ts +247 -0
- package/src/modules/groups/__tests__/groups.routes.test.ts +493 -0
- package/src/modules/groups/__tests__/join-proposals.test.ts +101 -0
- package/src/modules/groups/__tests__/join-requests.service.test.ts +175 -0
- package/src/modules/groups/group-provision.service.ts +328 -0
- package/src/modules/groups/groups.contract.ts +107 -0
- package/src/modules/groups/groups.routes.ts +433 -0
- package/src/modules/groups/groups.service.ts +193 -0
- package/src/modules/groups/index.ts +17 -0
- package/src/modules/groups/join-proposals.ts +114 -0
- package/src/modules/groups/join-requests.service.ts +185 -0
- package/src/modules/secrets-vault/__tests__/oauth-return-to.route.test.ts +283 -0
- package/src/modules/secrets-vault/secrets-vault.routes.ts +68 -12
- package/src/modules/settings/__tests__/deployment-settings.service.test.ts +256 -0
- package/src/modules/settings/__tests__/setup.routes.test.ts +201 -0
- package/src/modules/settings/deployment-settings.service.ts +419 -0
- package/src/modules/settings/setup.routes.ts +317 -0
- package/src/modules/skills/__tests__/pending-skills.service.test.ts +240 -0
- package/src/modules/skills/__tests__/skills.service.test.ts +13 -13
- package/src/modules/skills/index.ts +3 -0
- package/src/modules/skills/pending-skills.service.ts +168 -0
- package/src/modules/skills/skills.contract.ts +34 -3
- package/src/modules/skills/skills.routes.ts +23 -2
- package/src/modules/skills/skills.service.ts +24 -16
- package/src/modules/tool-manuals/__tests__/tool-manuals.detail.route.test.ts +262 -0
- 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 +61 -23
- package/src/modules/tool-manuals/__tests__/tool-manuals.tools.test.ts +156 -156
- package/src/modules/tool-manuals/index.ts +2 -0
- package/src/modules/tool-manuals/tool-manuals.contract.ts +50 -2
- package/src/modules/tool-manuals/tool-manuals.routes.ts +21 -0
- package/src/modules/tool-manuals/tool-manuals.service.ts +86 -17
- package/src/modules/tool-manuals/tool-manuals.tools.ts +206 -202
- package/src/modules/workflow/__tests__/event-bus.test.ts +35 -0
- package/src/modules/workflow/__tests__/workflow.service.facade.test.ts +251 -5
- package/src/modules/workflow/agent-tools/workflow.tools.ts +4 -2
- package/src/modules/workflow/event-bus.ts +25 -0
- package/src/modules/workflow/file-lock.service.ts +17 -0
- package/src/modules/workflow/git/__tests__/git.service.accessGating.test.ts +9 -49
- package/src/modules/workflow/git/__tests__/git.service.changedFilesForPr.test.ts +101 -0
- package/src/modules/workflow/git/__tests__/git.service.commitFile.test.ts +219 -211
- package/src/modules/workflow/git/__tests__/git.service.fileContentsAtCommit.test.ts +132 -0
- package/src/modules/workflow/git/__tests__/git.service.logForFile.test.ts +4 -2
- package/src/modules/workflow/git/__tests__/git.service.postMergePull.test.ts +275 -272
- package/src/modules/workflow/git/__tests__/git.service.pull.test.ts +5 -0
- package/src/modules/workflow/git/__tests__/pull-request.service.viewer-can-cancel.test.ts +44 -0
- package/src/modules/workflow/git/branch-name.ts +5 -1
- package/src/modules/workflow/git/git.service.ts +159 -62
- package/src/modules/workflow/git/pull-request.service.ts +22 -11
- package/src/modules/workflow/review-workflow/__tests__/cancel-pr.test.ts +4 -1
- package/src/modules/workflow/review-workflow/review-workflow.service.ts +12 -3
- package/src/modules/workflow/workflow.routes.ts +91 -13
- package/src/modules/workflow/workflow.service.ts +353 -13
- package/src/modules/workspace/__tests__/kb-seed.service.test.ts +255 -24
- package/src/modules/workspace/__tests__/workspace.routes.create-grant.test.ts +236 -233
- package/src/modules/workspace/__tests__/workspace.routes.delete.test.ts +179 -176
- package/src/modules/workspace/__tests__/workspace.routes.download.test.ts +320 -303
- package/src/modules/workspace/__tests__/workspace.routes.read-gate.test.ts +337 -276
- package/src/modules/workspace/__tests__/workspace.tools.test.ts +14 -6
- package/src/modules/workspace/bevel-ignore.ts +66 -62
- package/src/modules/workspace/kb-seed.interface.ts +1 -1
- package/src/modules/workspace/kb-seed.service.ts +247 -53
- package/src/modules/workspace/workspace.routes.ts +54 -18
- package/src/modules/workspace/workspace.service.ts +45 -7
- package/src/modules/workspace/workspace.tools.ts +922 -917
- package/src/shared/__tests__/frontmatter-id.test.ts +53 -0
- package/src/shared/__tests__/join-request.test.ts +96 -0
- package/src/shared/__tests__/kb-layout.group.test.ts +45 -0
- package/src/shared/kb-layout.test.ts +2 -2
- package/src/test-setup.ts +18 -0
- package/kb-template/Agents/README.md +0 -38
- package/kb-template/CLAUDE.md +0 -419
- package/kb-template/Data/.gitkeep +0 -0
- package/kb-template/Data/README.md +0 -39
- package/kb-template/KnowledgeBase/README.md +0 -37
- package/kb-template/Pipelines/.gitkeep +0 -0
- package/kb-template/Pipelines/README.md +0 -38
- package/kb-template/Skills/.gitkeep +0 -0
- package/kb-template/Skills/README.md +0 -32
- package/kb-template/Tools/.gitkeep +0 -0
- package/kb-template/Tools/README.md +0 -42
- /package/kb-template/{Agents → Groups}/.gitkeep +0 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import type { IAdminAccessService } from '../admin/admin.interface.js';
|
|
3
|
+
import { DeploymentSettingsService } from './deployment-settings.service.js';
|
|
4
|
+
import '../auth/auth.middleware.js';
|
|
5
|
+
/**
|
|
6
|
+
* First-run setup — the deployment's own configuration, for the things that
|
|
7
|
+
* used to be environment-only.
|
|
8
|
+
*
|
|
9
|
+
* WHY THESE ROUTES ARE AUTHENTICATED BUT NOT KB-DEPENDENT: the whole point is
|
|
10
|
+
* to be reachable on a deployment that has no knowledge base yet. That works
|
|
11
|
+
* because the bootstrap admin (`ADMIN_EMAIL`) is recognised without consulting
|
|
12
|
+
* `roles.yaml` — `AdminAccessService` short-circuits on it before any clone is
|
|
13
|
+
* attempted — so the one person who can finish setup can always sign in, and
|
|
14
|
+
* nobody else is let near it.
|
|
15
|
+
*/
|
|
16
|
+
export declare function createSetupRoutes(settings: DeploymentSettingsService, adminAccess: IAdminAccessService): express.Router;
|
|
17
|
+
/**
|
|
18
|
+
* Whether the STORED configuration answers everything the deployment needs.
|
|
19
|
+
*
|
|
20
|
+
* The KB needs a URL and a token — the username and the directory name both
|
|
21
|
+
* have working defaults, so neither can block a start. The branch model has no
|
|
22
|
+
* default ON PURPOSE: guessing `main` would silently point a deployment at the
|
|
23
|
+
* wrong branch and let the protected-branch guards apply to nothing, which is
|
|
24
|
+
* worse than asking.
|
|
25
|
+
*
|
|
26
|
+
* SSO is deliberately absent. It is configuration, not a prerequisite — a
|
|
27
|
+
* deployment signs in perfectly well without it, and gating on it would lock
|
|
28
|
+
* an admin out of the screen where they would set it up.
|
|
29
|
+
*/
|
|
30
|
+
export declare function settingsAnswered(settings: DeploymentSettingsService): boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Whether THIS PROCESS can actually serve — which is not the same question,
|
|
33
|
+
* and conflating them opened the gate onto a broken app.
|
|
34
|
+
*
|
|
35
|
+
* The branch model is applied once, during boot: services take `DEFAULT_BRANCH`
|
|
36
|
+
* at construction and the browser is served it before it renders. Saving it
|
|
37
|
+
* therefore answers the question without changing the answer this process
|
|
38
|
+
* holds — so a deployment configured through the setup screen reported itself
|
|
39
|
+
* complete while every workspace call still failed with
|
|
40
|
+
* `Invalid branch name ""`.
|
|
41
|
+
*
|
|
42
|
+
* Requiring the model to be IN EFFECT keeps the gate shut until the restart
|
|
43
|
+
* that puts it there. {@link awaitingRestart} is what tells the screen to ask
|
|
44
|
+
* for one rather than claim a field is missing.
|
|
45
|
+
*/
|
|
46
|
+
export declare function isComplete(settings: DeploymentSettingsService): boolean;
|
|
47
|
+
/** Answered, but not yet in effect: everything is stored, the process is stale. */
|
|
48
|
+
export declare function awaitingRestart(settings: DeploymentSettingsService): boolean;
|
|
49
|
+
//# sourceMappingURL=setup.routes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup.routes.d.ts","sourceRoot":"","sources":["../../../src/modules/settings/setup.routes.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,SAAS,CAAC;AAG9B,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AACvE,OAAO,EACL,yBAAyB,EAG1B,MAAM,kCAAkC,CAAC;AAM1C,OAAO,4BAA4B,CAAC;AAIpC;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,yBAAyB,EACnC,WAAW,EAAE,mBAAmB,GAC/B,OAAO,CAAC,MAAM,CAqNhB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,yBAAyB,GAAG,OAAO,CAQ7E;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,yBAAyB,GAAG,OAAO,CAEvE;AAED,mFAAmF;AACnF,wBAAgB,eAAe,CAAC,QAAQ,EAAE,yBAAyB,GAAG,OAAO,CAE5E"}
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import { execFile } from 'node:child_process';
|
|
3
|
+
import { promisify } from 'node:util';
|
|
4
|
+
import { SettingsValidationError, validateHttpsRemote, } from './deployment-settings.service.js';
|
|
5
|
+
import { configureBranchModel, isBranchModelConfigured, validateBranchModel, } from '@bevel-software/platform-shared';
|
|
6
|
+
import '../auth/auth.middleware.js'; // Express Request augmentation
|
|
7
|
+
const execFileAsync = promisify(execFile);
|
|
8
|
+
/**
|
|
9
|
+
* First-run setup — the deployment's own configuration, for the things that
|
|
10
|
+
* used to be environment-only.
|
|
11
|
+
*
|
|
12
|
+
* WHY THESE ROUTES ARE AUTHENTICATED BUT NOT KB-DEPENDENT: the whole point is
|
|
13
|
+
* to be reachable on a deployment that has no knowledge base yet. That works
|
|
14
|
+
* because the bootstrap admin (`ADMIN_EMAIL`) is recognised without consulting
|
|
15
|
+
* `roles.yaml` — `AdminAccessService` short-circuits on it before any clone is
|
|
16
|
+
* attempted — so the one person who can finish setup can always sign in, and
|
|
17
|
+
* nobody else is let near it.
|
|
18
|
+
*/
|
|
19
|
+
export function createSetupRoutes(settings, adminAccess) {
|
|
20
|
+
const router = express.Router();
|
|
21
|
+
const requireAdmin = async (req, res, next) => {
|
|
22
|
+
if (!(await adminAccess.isAdmin(req.userEmail))) {
|
|
23
|
+
res.status(403).json({ error: 'Admins only' });
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
next();
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Whether the deployment is usable, and — for an admin — what is missing.
|
|
30
|
+
*
|
|
31
|
+
* Deliberately readable by ANY signed-in user, because everyone needs the
|
|
32
|
+
* answer: a non-admin who arrives mid-setup gets told the deployment is
|
|
33
|
+
* still being configured instead of a broken file tree. Only the admin
|
|
34
|
+
* branch carries the settings themselves, and no branch ever carries a
|
|
35
|
+
* secret's value.
|
|
36
|
+
*/
|
|
37
|
+
router.get('/setup/status', async (req, res) => {
|
|
38
|
+
const complete = isComplete(settings);
|
|
39
|
+
if (!(await adminAccess.isAdmin(req.userEmail))) {
|
|
40
|
+
res.json({ complete, isAdmin: false });
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
res.json({
|
|
44
|
+
complete,
|
|
45
|
+
awaitingRestart: awaitingRestart(settings),
|
|
46
|
+
isAdmin: true,
|
|
47
|
+
settings: settings.describe(),
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
/**
|
|
51
|
+
* Save settings. Validated and written as ONE batch — a repository URL
|
|
52
|
+
* stored without the token that reads it is a deployment that fails at its
|
|
53
|
+
* first clone, so a partial write is never better than none.
|
|
54
|
+
*/
|
|
55
|
+
router.post('/setup/settings', requireAdmin, async (req, res) => {
|
|
56
|
+
const body = (req.body ?? {});
|
|
57
|
+
const entries = {};
|
|
58
|
+
for (const [key, value] of Object.entries(body.settings ?? {})) {
|
|
59
|
+
if (typeof value !== 'string') {
|
|
60
|
+
res.status(400).json({ error: `"${key}" must be a string.` });
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
entries[key] = value;
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
const { restartRequired } = await settings.save(entries, req.userId ?? null);
|
|
67
|
+
/**
|
|
68
|
+
* Apply the branch model to THIS process, so pressing Save finishes
|
|
69
|
+
* setup instead of asking for a restart.
|
|
70
|
+
*
|
|
71
|
+
* Only when it was not already configured — reconfiguring a live
|
|
72
|
+
* deployment mid-flight would swap the branches out from under sessions
|
|
73
|
+
* that are using them, which is a different and much less welcome
|
|
74
|
+
* feature. Going from "none" to "some" has nobody to disturb: the app is
|
|
75
|
+
* gated shut until exactly this moment.
|
|
76
|
+
*
|
|
77
|
+
* The services that need it read it when they use it rather than
|
|
78
|
+
* capturing it at construction, which is what makes applying it here
|
|
79
|
+
* enough.
|
|
80
|
+
*/
|
|
81
|
+
if (!isBranchModelConfigured()) {
|
|
82
|
+
const model = {
|
|
83
|
+
defaultBranch: settings.resolve('defaultBranch'),
|
|
84
|
+
protectedBranches: settings.resolve('protectedBranches'),
|
|
85
|
+
};
|
|
86
|
+
if (!validateBranchModel(model))
|
|
87
|
+
configureBranchModel(model);
|
|
88
|
+
}
|
|
89
|
+
res.json({
|
|
90
|
+
ok: true,
|
|
91
|
+
restartRequired,
|
|
92
|
+
complete: isComplete(settings),
|
|
93
|
+
awaitingRestart: awaitingRestart(settings),
|
|
94
|
+
settings: settings.describe(),
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
catch (err) {
|
|
98
|
+
if (err instanceof SettingsValidationError) {
|
|
99
|
+
res.status(400).json({ error: 'Some settings need fixing.', problems: err.problems });
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
// Logged in full, returned generic: a driver message here would hand back
|
|
103
|
+
// the schema or the connection string.
|
|
104
|
+
console.error('[setup] save failed:', err instanceof Error ? err.message : String(err));
|
|
105
|
+
res.status(500).json({ error: 'Could not save these settings.' });
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
/**
|
|
109
|
+
* Try the credentials against the real remote, BEFORE anything is saved.
|
|
110
|
+
*
|
|
111
|
+
* This is the reason the screen is worth more than the environment variables
|
|
112
|
+
* it replaces. `ls-remote` is the cheapest operation that proves all three
|
|
113
|
+
* values at once — the URL resolves, the token authenticates, and the
|
|
114
|
+
* username is the one this host expects — and it answers in a second instead
|
|
115
|
+
* of surfacing as a failed clone at some later, unrelated moment.
|
|
116
|
+
*
|
|
117
|
+
* Values are taken from the request when supplied so an admin can test what
|
|
118
|
+
* they typed rather than what is stored, and fall back to what is in effect
|
|
119
|
+
* (which is how "test the token I saved last week" works).
|
|
120
|
+
*/
|
|
121
|
+
router.post('/setup/test-connection', requireAdmin, async (req, res) => {
|
|
122
|
+
const body = (req.body ?? {});
|
|
123
|
+
const supplied = (key) => {
|
|
124
|
+
const value = body[key];
|
|
125
|
+
return typeof value === 'string' && value.trim() ? value.trim() : null;
|
|
126
|
+
};
|
|
127
|
+
const url = supplied('kbRepoUrl') ?? settings.resolve('kbRepoUrl');
|
|
128
|
+
// Unlike the token, the username is not a secret and carries a default —
|
|
129
|
+
// it is which name the host expects beside the token, not a credential.
|
|
130
|
+
const username = supplied('gitUsername') ?? (settings.resolve('gitUsername') || 'x-access-token');
|
|
131
|
+
if (!url) {
|
|
132
|
+
res.status(400).json({ ok: false, error: 'Enter the repository URL first.' });
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* THE CONFIGURED TOKEN ONLY EVER GOES TO THE CONFIGURED REPOSITORY.
|
|
137
|
+
*
|
|
138
|
+
* The stored token is deliberately unreadable — `describe()` omits it, so
|
|
139
|
+
* an admin can replace it but never see it. Falling back to it for whatever
|
|
140
|
+
* URL the request names would hand that value straight back: point the test
|
|
141
|
+
* at a host you control, read it out of the request. Admin-gated, but the
|
|
142
|
+
* whole point of not returning it is that being an admin is not the same as
|
|
143
|
+
* being allowed to hold it.
|
|
144
|
+
*
|
|
145
|
+
* So a request that names a DIFFERENT repository has to bring its own
|
|
146
|
+
* credential. Testing what is already configured — the "does the token I
|
|
147
|
+
* saved last week still work?" case — is unaffected.
|
|
148
|
+
*/
|
|
149
|
+
const suppliedToken = supplied('gitToken');
|
|
150
|
+
const testingConfiguredRepo = url === settings.resolve('kbRepoUrl');
|
|
151
|
+
if (!suppliedToken && !testingConfiguredRepo) {
|
|
152
|
+
res.status(400).json({
|
|
153
|
+
ok: false,
|
|
154
|
+
error: 'Enter the access token for that repository — the saved one is only used with the repository it was saved for.',
|
|
155
|
+
});
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
const token = suppliedToken ?? (testingConfiguredRepo ? settings.resolve('gitToken') : '');
|
|
159
|
+
// The SAME rule the setting is validated by, applied before the value ever
|
|
160
|
+
// reaches git. Without it this endpoint is argument injection: a value
|
|
161
|
+
// beginning `--upload-pack=` makes git run a command of the caller's
|
|
162
|
+
// choosing, and `ext::sh -c …` is a transport whose entire purpose is to
|
|
163
|
+
// execute one. Both are admin-only, but "admin" is not "may run arbitrary
|
|
164
|
+
// commands as the server process".
|
|
165
|
+
const urlProblem = validateHttpsRemote(url);
|
|
166
|
+
if (urlProblem) {
|
|
167
|
+
res.status(400).json({ ok: false, error: urlProblem });
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
if (!/^[A-Za-z0-9._-]+$/.test(username)) {
|
|
171
|
+
// Interpolated into the credential-helper snippet below.
|
|
172
|
+
res.status(400).json({ ok: false, error: 'The username contains unsupported characters.' });
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
try {
|
|
176
|
+
// The helper reads the token from the environment at call time, so it
|
|
177
|
+
// never appears in argv (and so never in a process listing or a crash
|
|
178
|
+
// dump). Same shape the real clone uses.
|
|
179
|
+
const args = ['-c', 'credential.helper=', ...(token
|
|
180
|
+
? ['-c', `credential.helper=!f() { echo "username=${username}"; echo "password=$BEVEL_TEST_TOKEN"; }; f`]
|
|
181
|
+
: []),
|
|
182
|
+
// `--end-of-options` on top of the validation above: belt and braces,
|
|
183
|
+
// so nothing that arrives here can ever be read as a flag.
|
|
184
|
+
'ls-remote', '--heads', '--end-of-options', url];
|
|
185
|
+
const { stdout } = await execFileAsync('git', args, {
|
|
186
|
+
timeout: 20_000,
|
|
187
|
+
env: {
|
|
188
|
+
...process.env,
|
|
189
|
+
BEVEL_TEST_TOKEN: token,
|
|
190
|
+
// Never let git stop for a prompt: without this a bad credential
|
|
191
|
+
// hangs the request until the timeout instead of failing.
|
|
192
|
+
GIT_TERMINAL_PROMPT: '0',
|
|
193
|
+
GIT_ASKPASS: 'echo',
|
|
194
|
+
},
|
|
195
|
+
});
|
|
196
|
+
const lines = stdout.split('\n');
|
|
197
|
+
// `<sha>\trefs/heads/<name>` — tag refs and the bare HEAD row are not
|
|
198
|
+
// branches, so they are filtered rather than sliced blindly.
|
|
199
|
+
const branches = lines
|
|
200
|
+
.filter((line) => !line.startsWith('ref:'))
|
|
201
|
+
.map((line) => line.split('\t')[1]?.trim())
|
|
202
|
+
.filter((ref) => !!ref && ref.startsWith('refs/heads/'))
|
|
203
|
+
.map((ref) => ref.slice('refs/heads/'.length));
|
|
204
|
+
// `ref: refs/heads/<name>\tHEAD` — what the remote calls its own trunk.
|
|
205
|
+
const defaultBranch = lines
|
|
206
|
+
.find((line) => line.startsWith('ref:') && line.trimEnd().endsWith('HEAD'))
|
|
207
|
+
?.slice('ref: refs/heads/'.length)
|
|
208
|
+
.split('\t')[0]
|
|
209
|
+
?.trim() || null;
|
|
210
|
+
res.json({
|
|
211
|
+
ok: true,
|
|
212
|
+
// An EMPTY repository is a success, not a failure — seeding one is a
|
|
213
|
+
// supported path, and saying "no branches yet" beats an error that
|
|
214
|
+
// reads like the credentials are wrong.
|
|
215
|
+
empty: branches.length === 0,
|
|
216
|
+
branches,
|
|
217
|
+
defaultBranch,
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
catch (err) {
|
|
221
|
+
res.status(200).json({ ok: false, error: explainGitFailure(err, token) });
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
return router;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Whether the STORED configuration answers everything the deployment needs.
|
|
228
|
+
*
|
|
229
|
+
* The KB needs a URL and a token — the username and the directory name both
|
|
230
|
+
* have working defaults, so neither can block a start. The branch model has no
|
|
231
|
+
* default ON PURPOSE: guessing `main` would silently point a deployment at the
|
|
232
|
+
* wrong branch and let the protected-branch guards apply to nothing, which is
|
|
233
|
+
* worse than asking.
|
|
234
|
+
*
|
|
235
|
+
* SSO is deliberately absent. It is configuration, not a prerequisite — a
|
|
236
|
+
* deployment signs in perfectly well without it, and gating on it would lock
|
|
237
|
+
* an admin out of the screen where they would set it up.
|
|
238
|
+
*/
|
|
239
|
+
export function settingsAnswered(settings) {
|
|
240
|
+
const kb = Boolean(settings.resolve('kbRepoUrl') && settings.resolve('gitToken'));
|
|
241
|
+
const branches = validateBranchModel({
|
|
242
|
+
defaultBranch: settings.resolve('defaultBranch'),
|
|
243
|
+
protectedBranches: settings.resolve('protectedBranches'),
|
|
244
|
+
}) === null;
|
|
245
|
+
return kb && branches;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Whether THIS PROCESS can actually serve — which is not the same question,
|
|
249
|
+
* and conflating them opened the gate onto a broken app.
|
|
250
|
+
*
|
|
251
|
+
* The branch model is applied once, during boot: services take `DEFAULT_BRANCH`
|
|
252
|
+
* at construction and the browser is served it before it renders. Saving it
|
|
253
|
+
* therefore answers the question without changing the answer this process
|
|
254
|
+
* holds — so a deployment configured through the setup screen reported itself
|
|
255
|
+
* complete while every workspace call still failed with
|
|
256
|
+
* `Invalid branch name ""`.
|
|
257
|
+
*
|
|
258
|
+
* Requiring the model to be IN EFFECT keeps the gate shut until the restart
|
|
259
|
+
* that puts it there. {@link awaitingRestart} is what tells the screen to ask
|
|
260
|
+
* for one rather than claim a field is missing.
|
|
261
|
+
*/
|
|
262
|
+
export function isComplete(settings) {
|
|
263
|
+
return settingsAnswered(settings) && isBranchModelConfigured();
|
|
264
|
+
}
|
|
265
|
+
/** Answered, but not yet in effect: everything is stored, the process is stale. */
|
|
266
|
+
export function awaitingRestart(settings) {
|
|
267
|
+
return settingsAnswered(settings) && !isBranchModelConfigured();
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Turn git's stderr into something an admin can act on. Deliberately narrow:
|
|
271
|
+
* the raw text is echoed only when it matches nothing known, and the token is
|
|
272
|
+
* scrubbed from it first — `ls-remote` failures have been known to quote the
|
|
273
|
+
* credential back.
|
|
274
|
+
*/
|
|
275
|
+
function explainGitFailure(err, token) {
|
|
276
|
+
const raw = err instanceof Error ? `${err.message}` : String(err);
|
|
277
|
+
const text = token ? raw.replaceAll(token, '***') : raw;
|
|
278
|
+
if (/timed out|ETIMEDOUT/i.test(text)) {
|
|
279
|
+
return 'The host did not answer in time. Check the URL, and that this server can reach it.';
|
|
280
|
+
}
|
|
281
|
+
if (/Authentication failed|could not read Username|invalid credentials|403/i.test(text)) {
|
|
282
|
+
return 'The host rejected those credentials. Check the token, and that the username matches the host (GitHub x-access-token, GitLab oauth2, Bitbucket x-token-auth).';
|
|
283
|
+
}
|
|
284
|
+
if (/not found|repository .* does not exist|404/i.test(text)) {
|
|
285
|
+
return 'No repository at that URL — or the token cannot see it.';
|
|
286
|
+
}
|
|
287
|
+
if (/could not resolve host|unable to access|SSL|certificate/i.test(text)) {
|
|
288
|
+
return 'Could not reach that host from this server. Check the URL and any network egress rules.';
|
|
289
|
+
}
|
|
290
|
+
return text.split('\n').slice(0, 3).join(' ').slice(0, 400);
|
|
291
|
+
}
|
|
292
|
+
//# sourceMappingURL=setup.routes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup.routes.js","sourceRoot":"","sources":["../../../src/modules/settings/setup.routes.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { SkillService } from './skills.service.js';
|
|
2
|
+
export { PendingSkillsService } from './pending-skills.service.js';
|
|
2
3
|
export { registerSkillsTools } from './skills.tools.js';
|
|
3
4
|
export { createSkillsRoutes } from './skills.routes.js';
|
|
4
|
-
export type { ISkillService, Skill, SkillSummary, SkillFileContent, GetSkillResult, } from './skills.contract.js';
|
|
5
|
+
export type { ISkillService, IPendingSkillService, Skill, SkillSummary, SkillFileContent, PendingSkill, GetSkillResult, } from './skills.contract.js';
|
|
5
6
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/modules/skills/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACxD,YAAY,EACV,aAAa,EACb,KAAK,EACL,YAAY,EACZ,gBAAgB,EAChB,cAAc,GACf,MAAM,sBAAsB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/modules/skills/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AACnE,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACxD,YAAY,EACV,aAAa,EACb,oBAAoB,EACpB,KAAK,EACL,YAAY,EACZ,gBAAgB,EAChB,YAAY,EACZ,cAAc,GACf,MAAM,sBAAsB,CAAC"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { SkillService } from './skills.service.js';
|
|
2
|
+
export { PendingSkillsService } from './pending-skills.service.js';
|
|
2
3
|
export { registerSkillsTools } from './skills.tools.js';
|
|
3
4
|
export { createSkillsRoutes } from './skills.routes.js';
|
|
4
5
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/modules/skills/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/modules/skills/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AACnE,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { type IWorkflowService } from '@bevel-software/platform-shared';
|
|
2
|
+
import { type WorkspaceService } from '../workspace/workspace.service.js';
|
|
3
|
+
import type { IAccessControl } from '../access/access-control.interface.js';
|
|
4
|
+
import type { IPendingSkillService, ISkillService, PendingSkill } from './skills.contract.js';
|
|
5
|
+
/**
|
|
6
|
+
* Skills that exist only on an open change request — proposed, not released.
|
|
7
|
+
*
|
|
8
|
+
* The catalog (`SkillService`) is pinned to the default branch, which is the
|
|
9
|
+
* right answer for everything that LOADS a skill and the wrong one for the
|
|
10
|
+
* person who just proposed it: a change request that ADDS a skill folder has
|
|
11
|
+
* nothing on the default branch to hang itself off, so until it merged the
|
|
12
|
+
* proposal was invisible to its author and to the people who had to approve it.
|
|
13
|
+
* This service is that missing half, and it is deliberately a separate surface
|
|
14
|
+
* rather than a widening of the catalog — an unapproved skill must never become
|
|
15
|
+
* loadable just because it became visible.
|
|
16
|
+
*
|
|
17
|
+
* WHO MAY SEE ONE is narrower than who may see the catalog: the author, and
|
|
18
|
+
* whoever could approve it. "Could approve it" is not re-stated here as a
|
|
19
|
+
* group-admin check — it is `canWrite` on the SKILL.md the request would
|
|
20
|
+
* create. The new folder carries no `access.md` of its own, so that verdict
|
|
21
|
+
* inherits from the group folder, which IS the group's admin; asking the access
|
|
22
|
+
* tree keeps this surface and the merge gate answering with one voice, and it
|
|
23
|
+
* keeps working for skills nested in a category subfolder that carries rules of
|
|
24
|
+
* its own.
|
|
25
|
+
*
|
|
26
|
+
* Everything degrades to "nothing pending" rather than throwing: this hangs off
|
|
27
|
+
* the library's list load, and a review surface that cannot answer must not
|
|
28
|
+
* take the shelf down with it.
|
|
29
|
+
*/
|
|
30
|
+
export declare class PendingSkillsService implements IPendingSkillService {
|
|
31
|
+
private readonly workspaceService;
|
|
32
|
+
private readonly accessControl;
|
|
33
|
+
private readonly skillService;
|
|
34
|
+
private readonly workflow;
|
|
35
|
+
constructor(workspaceService: WorkspaceService, accessControl: IAccessControl, skillService: ISkillService, workflow: IWorkflowService);
|
|
36
|
+
listPendingSkills(userEmail: string): Promise<PendingSkill[]>;
|
|
37
|
+
/** The file at a branch, or null when it is absent/unreadable there. */
|
|
38
|
+
private readAt;
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=pending-skills.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pending-skills.service.d.ts","sourceRoot":"","sources":["../../../src/modules/skills/pending-skills.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,gBAAgB,EACtB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAwB,KAAK,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AAChG,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAC;AAI5E,OAAO,KAAK,EAAE,oBAAoB,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAI9F;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,qBAAa,oBAAqB,YAAW,oBAAoB;IAE7D,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IACjC,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,QAAQ;gBAHR,gBAAgB,EAAE,gBAAgB,EAClC,aAAa,EAAE,cAAc,EAC7B,YAAY,EAAE,aAAa,EAC3B,QAAQ,EAAE,gBAAgB;IAGvC,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAqFnE,wEAAwE;YAC1D,MAAM;CAWrB"}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { DEFAULT_BRANCH, GROUPS_DIR, } from '@bevel-software/platform-shared';
|
|
2
|
+
import { workspaceIdForBranch } from '../workspace/workspace.service.js';
|
|
3
|
+
import { hashEmail } from '../../shared/hash-email.js';
|
|
4
|
+
import { resolveDeclaredId } from '../../shared/frontmatter-id.js';
|
|
5
|
+
import { isSafeSkillName, parseSkillFrontmatter } from './skills.service.js';
|
|
6
|
+
const SKILL_DOC = 'SKILL.md';
|
|
7
|
+
/**
|
|
8
|
+
* Skills that exist only on an open change request — proposed, not released.
|
|
9
|
+
*
|
|
10
|
+
* The catalog (`SkillService`) is pinned to the default branch, which is the
|
|
11
|
+
* right answer for everything that LOADS a skill and the wrong one for the
|
|
12
|
+
* person who just proposed it: a change request that ADDS a skill folder has
|
|
13
|
+
* nothing on the default branch to hang itself off, so until it merged the
|
|
14
|
+
* proposal was invisible to its author and to the people who had to approve it.
|
|
15
|
+
* This service is that missing half, and it is deliberately a separate surface
|
|
16
|
+
* rather than a widening of the catalog — an unapproved skill must never become
|
|
17
|
+
* loadable just because it became visible.
|
|
18
|
+
*
|
|
19
|
+
* WHO MAY SEE ONE is narrower than who may see the catalog: the author, and
|
|
20
|
+
* whoever could approve it. "Could approve it" is not re-stated here as a
|
|
21
|
+
* group-admin check — it is `canWrite` on the SKILL.md the request would
|
|
22
|
+
* create. The new folder carries no `access.md` of its own, so that verdict
|
|
23
|
+
* inherits from the group folder, which IS the group's admin; asking the access
|
|
24
|
+
* tree keeps this surface and the merge gate answering with one voice, and it
|
|
25
|
+
* keeps working for skills nested in a category subfolder that carries rules of
|
|
26
|
+
* its own.
|
|
27
|
+
*
|
|
28
|
+
* Everything degrades to "nothing pending" rather than throwing: this hangs off
|
|
29
|
+
* the library's list load, and a review surface that cannot answer must not
|
|
30
|
+
* take the shelf down with it.
|
|
31
|
+
*/
|
|
32
|
+
export class PendingSkillsService {
|
|
33
|
+
workspaceService;
|
|
34
|
+
accessControl;
|
|
35
|
+
skillService;
|
|
36
|
+
workflow;
|
|
37
|
+
constructor(workspaceService, accessControl, skillService, workflow) {
|
|
38
|
+
this.workspaceService = workspaceService;
|
|
39
|
+
this.accessControl = accessControl;
|
|
40
|
+
this.skillService = skillService;
|
|
41
|
+
this.workflow = workflow;
|
|
42
|
+
}
|
|
43
|
+
async listPendingSkills(userEmail) {
|
|
44
|
+
const email = userEmail.trim().toLowerCase();
|
|
45
|
+
if (!email)
|
|
46
|
+
return [];
|
|
47
|
+
let crs;
|
|
48
|
+
try {
|
|
49
|
+
crs = (await this.workflow.listChangeRequests()).filter((c) => c.state === 'open');
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return [];
|
|
53
|
+
}
|
|
54
|
+
if (crs.length === 0)
|
|
55
|
+
return [];
|
|
56
|
+
// The released set, UNFILTERED by the caller's read access. A skill someone
|
|
57
|
+
// else can read and this caller cannot is still released — counting it as
|
|
58
|
+
// pending would invent a review that is not happening, and would leak the
|
|
59
|
+
// fact that the folder exists.
|
|
60
|
+
let released;
|
|
61
|
+
try {
|
|
62
|
+
released = new Set((await this.skillService.listSkills()).map((s) => s.path));
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return [];
|
|
66
|
+
}
|
|
67
|
+
const candidates = crs.flatMap((cr) => cr.touchedNodePaths
|
|
68
|
+
.filter((p) => isSkillDoc(p) && !released.has(folderOf(p)))
|
|
69
|
+
.map((p) => ({ cr, docPath: p, folder: folderOf(p) })));
|
|
70
|
+
if (candidates.length === 0)
|
|
71
|
+
return [];
|
|
72
|
+
const wsId = workspaceIdForBranch(DEFAULT_BRANCH);
|
|
73
|
+
const authorHash = hashEmail(email);
|
|
74
|
+
// One access load for every candidate. Resolved on the DEFAULT branch —
|
|
75
|
+
// the tree as it stands is what says who owns the group the skill is
|
|
76
|
+
// arriving into; the branch's own copy is written by the proposer and must
|
|
77
|
+
// not get a say in who reviews them.
|
|
78
|
+
const writable = await this.accessControl
|
|
79
|
+
.canWriteBatch(wsId, email, candidates.map((c) => c.docPath))
|
|
80
|
+
.catch(() => new Map());
|
|
81
|
+
// The branch copies are read through the default branch's clone at
|
|
82
|
+
// `origin/<branch>`, so a proposal never needs a clone of its own. Refresh
|
|
83
|
+
// first, best-effort: a request pushed seconds ago is worth one fetch, and
|
|
84
|
+
// a failed fetch should degrade to "read what we have".
|
|
85
|
+
await this.workspaceService.ensureRemotesFetched(wsId).catch(() => undefined);
|
|
86
|
+
const out = [];
|
|
87
|
+
for (const { cr, docPath, folder } of candidates) {
|
|
88
|
+
const isAuthor = !!cr.authorId && cr.authorId === authorHash;
|
|
89
|
+
// Fail closed, matching the catalog: only an explicit `true` admits a
|
|
90
|
+
// reviewer. A missing entry (the batch skipped the path, the tree could
|
|
91
|
+
// not be read) means denied, never shown.
|
|
92
|
+
if (!isAuthor && writable.get(docPath) !== true)
|
|
93
|
+
continue;
|
|
94
|
+
const raw = await this.readAt(wsId, cr.branch, docPath);
|
|
95
|
+
// Nothing to describe: the request touched the path but the file is gone
|
|
96
|
+
// at its head (proposed then withdrawn within the branch), or unreadable.
|
|
97
|
+
if (raw === null)
|
|
98
|
+
continue;
|
|
99
|
+
const fm = parseSkillFrontmatter(raw);
|
|
100
|
+
const folderName = folder.split('/').pop() ?? folder;
|
|
101
|
+
const declared = resolveDeclaredId(fm.frontmatter, folderName);
|
|
102
|
+
out.push({
|
|
103
|
+
name: isSafeSkillName(declared) ? declared : folderName,
|
|
104
|
+
description: fm.description,
|
|
105
|
+
version: fm.version,
|
|
106
|
+
path: folder,
|
|
107
|
+
changeRequestNumber: cr.number,
|
|
108
|
+
branch: cr.branch,
|
|
109
|
+
authorName: cr.appAuthor?.name ?? cr.author.name ?? 'Someone',
|
|
110
|
+
createdAt: cr.createdAt,
|
|
111
|
+
isAuthor,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
// Oldest first — the one that has been waiting longest is the one that
|
|
115
|
+
// needs answering, and a list that reorders as requests arrive moves under
|
|
116
|
+
// the pointer.
|
|
117
|
+
return out.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
118
|
+
}
|
|
119
|
+
/** The file at a branch, or null when it is absent/unreadable there. */
|
|
120
|
+
async readAt(wsId, branch, repoRelPath) {
|
|
121
|
+
try {
|
|
122
|
+
return await this.workspaceService.readFileAtRef(wsId, `origin/${branch}`, repoRelPath);
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Is this touched path a skill's own SKILL.md?
|
|
131
|
+
*
|
|
132
|
+
* `Groups/<group>/<skill>/SKILL.md` is the shallowest shape, hence four
|
|
133
|
+
* segments; deeper ones are skills nested in a category subfolder, which the
|
|
134
|
+
* catalog already supports. `Groups/<group>/SKILL.md` is NOT a skill — a group
|
|
135
|
+
* folder is not itself one.
|
|
136
|
+
*/
|
|
137
|
+
function isSkillDoc(repoRelPath) {
|
|
138
|
+
const segments = repoRelPath.split('/');
|
|
139
|
+
return (segments.length >= 4 &&
|
|
140
|
+
segments[0] === GROUPS_DIR &&
|
|
141
|
+
segments[segments.length - 1] === SKILL_DOC);
|
|
142
|
+
}
|
|
143
|
+
/** The skill folder holding a SKILL.md. */
|
|
144
|
+
function folderOf(skillDocPath) {
|
|
145
|
+
return skillDocPath.slice(0, -(SKILL_DOC.length + 1));
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=pending-skills.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pending-skills.service.js","sourceRoot":"","sources":["../../../src/modules/skills/pending-skills.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Skills are reusable specialist instructions living under `
|
|
2
|
+
* Skills are reusable specialist instructions living under `Groups/<group>/<name>/SKILL.md`
|
|
3
3
|
* in the KB repo. Discovery/loading is pinned to the DEFAULT branch — the catalog
|
|
4
4
|
* is a single global, released set (a skill on a draft isn't discoverable until
|
|
5
5
|
* merged). Progressive disclosure: `listSkills` = name + description (level 1),
|
|
@@ -11,7 +11,7 @@ export interface SkillSummary {
|
|
|
11
11
|
name: string;
|
|
12
12
|
description: string;
|
|
13
13
|
version?: string;
|
|
14
|
-
/** Repo-root-relative skill folder, e.g. `
|
|
14
|
+
/** Repo-root-relative skill folder, e.g. `Groups/Everyone/rfi`. */
|
|
15
15
|
path: string;
|
|
16
16
|
}
|
|
17
17
|
export interface Skill extends SkillSummary {
|
|
@@ -19,7 +19,7 @@ export interface Skill extends SkillSummary {
|
|
|
19
19
|
body: string;
|
|
20
20
|
/** Pre-approved tools from the `allowed-tools` frontmatter, if declared. */
|
|
21
21
|
allowedTools?: string[];
|
|
22
|
-
/** Repo-root-relative bundled file paths, e.g. `
|
|
22
|
+
/** Repo-root-relative bundled file paths, e.g. `Groups/Everyone/rfi/scripts/build_xlsx.py`. */
|
|
23
23
|
files: string[];
|
|
24
24
|
}
|
|
25
25
|
export interface SkillFileContent {
|
|
@@ -43,6 +43,35 @@ export type GetSkillResult = {
|
|
|
43
43
|
ok: false;
|
|
44
44
|
error: 'not_found' | 'forbidden' | 'invalid_file';
|
|
45
45
|
};
|
|
46
|
+
/**
|
|
47
|
+
* A skill that exists ONLY on an open change request's branch — proposed, not
|
|
48
|
+
* released. It is deliberately NOT a `SkillSummary` the catalog returns: the
|
|
49
|
+
* catalog is what agents load, and a skill nobody has approved must not be
|
|
50
|
+
* loadable. This is a review surface only.
|
|
51
|
+
*
|
|
52
|
+
* Who may see one is narrower than who may see the catalog: its author, and
|
|
53
|
+
* whoever could approve it. See `PendingSkillsService` for why that predicate
|
|
54
|
+
* is the access tree's answer rather than a group-admin check spelled out
|
|
55
|
+
* again here.
|
|
56
|
+
*/
|
|
57
|
+
export interface PendingSkill extends SkillSummary {
|
|
58
|
+
/** The open change request that would release it. */
|
|
59
|
+
changeRequestNumber: number;
|
|
60
|
+
branch: string;
|
|
61
|
+
/** Display name of whoever opened the request (person or agent). */
|
|
62
|
+
authorName: string;
|
|
63
|
+
createdAt: string;
|
|
64
|
+
/** True when the caller opened the request themselves. */
|
|
65
|
+
isAuthor: boolean;
|
|
66
|
+
}
|
|
67
|
+
export interface IPendingSkillService {
|
|
68
|
+
/**
|
|
69
|
+
* Skills awaiting approval that `userEmail` is entitled to see — the ones
|
|
70
|
+
* they proposed, and the ones they could approve. Never throws: a review
|
|
71
|
+
* surface failing must not take the library down with it.
|
|
72
|
+
*/
|
|
73
|
+
listPendingSkills(userEmail: string): Promise<PendingSkill[]>;
|
|
74
|
+
}
|
|
46
75
|
export interface ISkillService {
|
|
47
76
|
/**
|
|
48
77
|
* The default-branch skill catalog. When `userEmail` is given, filtered to
|