@bevel-software/platform-core-backend 0.4.1 → 0.5.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/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 +3 -3
- 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
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { IGNORE_FILENAME } from './bevel-ignore.js';
|
|
4
|
+
import type { IAdminAccessService } from '../admin/admin.interface.js';
|
|
3
5
|
import express from 'express';
|
|
4
6
|
import type { AuthUser, IWorkflowService } from '@bevel-software/platform-shared';
|
|
5
7
|
import {
|
|
@@ -9,8 +11,7 @@ import {
|
|
|
9
11
|
KNOWLEDGE_BASE_DIR,
|
|
10
12
|
KNOWLEDGE_DIR,
|
|
11
13
|
PIPELINES_DIR,
|
|
12
|
-
|
|
13
|
-
TOOLS_DIR,
|
|
14
|
+
GROUPS_DIR,
|
|
14
15
|
} from '@bevel-software/platform-shared';
|
|
15
16
|
import { branchForWorkspaceId, FolderTooLargeError, type ReadTreeFilter } from './workspace.service.js';
|
|
16
17
|
import type { WorkspaceService } from './workspace.service.js';
|
|
@@ -48,6 +49,7 @@ export function createWorkspaceRoutes(
|
|
|
48
49
|
accessControl: IAccessControl,
|
|
49
50
|
kbDirName: string,
|
|
50
51
|
creatorAccess: ICreatorAccess,
|
|
52
|
+
adminAccess: IAdminAccessService,
|
|
51
53
|
): express.Router {
|
|
52
54
|
const router = express.Router();
|
|
53
55
|
|
|
@@ -463,26 +465,45 @@ export function createWorkspaceRoutes(
|
|
|
463
465
|
// The structural top-level folders are always shown as folders, even to
|
|
464
466
|
// a user who can't read into them. Their existence isn't sensitive
|
|
465
467
|
// (every KB has them), and keeping them visible lets the explorer render
|
|
466
|
-
// its Knowledge/
|
|
467
|
-
// flat tree.
|
|
468
|
-
//
|
|
469
|
-
//
|
|
470
|
-
//
|
|
471
|
-
//
|
|
472
|
-
// contents stay gated by the verdict above.
|
|
468
|
+
// its Knowledge/Groups section view instead of collapsing to an empty
|
|
469
|
+
// flat tree. A ROOT-LEVEL `Knowledge/` is the legacy pre-split layout's
|
|
470
|
+
// knowledge root (kb-layout.ts calls it the neutral bucket) and gets the
|
|
471
|
+
// same treatment so legacy clones don't collapse. Only the folders
|
|
472
|
+
// themselves are forced visible — their contents stay gated by the
|
|
473
|
+
// verdict above.
|
|
473
474
|
const structuralRoots = new Set([
|
|
474
475
|
KNOWLEDGE_BASE_DIR,
|
|
475
476
|
DATA_DIR,
|
|
476
477
|
AGENTS_DIR,
|
|
477
478
|
PIPELINES_DIR,
|
|
478
|
-
|
|
479
|
-
TOOLS_DIR,
|
|
479
|
+
GROUPS_DIR,
|
|
480
480
|
KNOWLEDGE_DIR,
|
|
481
481
|
]);
|
|
482
482
|
for (const wp of wsRelPaths) {
|
|
483
483
|
const rel = toKbRelative(wp, kbDirName);
|
|
484
484
|
if (rel !== null && structuralRoots.has(rel)) verdict.set(wp, true);
|
|
485
485
|
}
|
|
486
|
+
|
|
487
|
+
// `.bevelignore` is ADMIN-ONLY. It is the file that decides what the file
|
|
488
|
+
// tree and the agent view show at all, so it is deployment configuration
|
|
489
|
+
// rather than knowledge — and it sits alone in being visible: its
|
|
490
|
+
// siblings (`.gitignore`, `roles.yaml`, `access.md`, `AGENTS.md`) are
|
|
491
|
+
// already hidden from every reader by the shipped ignore rules. Somebody
|
|
492
|
+
// who cannot act on it gains a puzzle; somebody who can needs to reach it.
|
|
493
|
+
//
|
|
494
|
+
// Read permission is deliberately NOT the lever. Everyone can read it —
|
|
495
|
+
// it has to be readable to be applied — so hiding it is a listing
|
|
496
|
+
// decision, made here, rather than an ACL fiction maintained in a file.
|
|
497
|
+
//
|
|
498
|
+
// Resolved through `AdminAccessService` rather than by asking whether
|
|
499
|
+
// this caller can write `roles.yaml` in THIS workspace: admin is settled
|
|
500
|
+
// on the default branch precisely so that editing `roles.yaml` on your
|
|
501
|
+
// own branch cannot promote you, and the same reasoning applies to
|
|
502
|
+
// anything gated on being an admin.
|
|
503
|
+
const ignoreFiles = wsRelPaths.filter((wp) => path.basename(wp) === IGNORE_FILENAME);
|
|
504
|
+
if (ignoreFiles.length > 0 && !(await adminAccess.isAdmin(userEmail))) {
|
|
505
|
+
for (const wp of ignoreFiles) verdict.set(wp, false);
|
|
506
|
+
}
|
|
486
507
|
return verdict;
|
|
487
508
|
};
|
|
488
509
|
}
|
|
@@ -565,16 +586,25 @@ export function createWorkspaceRoutes(
|
|
|
565
586
|
'.xlsx':
|
|
566
587
|
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
567
588
|
};
|
|
568
|
-
// SVG is active web content (it can carry <script>)
|
|
569
|
-
//
|
|
570
|
-
//
|
|
571
|
-
// execute its scripts under the file:// origin — so when the user
|
|
572
|
-
// explicitly downloads, force octet-stream to defang it.
|
|
589
|
+
// SVG is active web content (it can carry <script>), and it is active in
|
|
590
|
+
// BOTH directions: a saved-to-disk SVG re-opened later runs its scripts
|
|
591
|
+
// under the file:// origin, so a download is forced to octet-stream.
|
|
573
592
|
const downloadMime = ext === '.svg' ? 'application/octet-stream' : (mimeTypes[ext] || 'application/octet-stream');
|
|
574
593
|
res.setHeader('Content-Type', wantsDownload ? downloadMime : (mimeTypes[ext] || 'application/octet-stream'));
|
|
575
594
|
// Block MIME-sniffing so a misdeclared file can't be promoted to
|
|
576
595
|
// active content by the browser.
|
|
577
596
|
res.setHeader('X-Content-Type-Options', 'nosniff');
|
|
597
|
+
// …and inline is a DOCUMENT the moment somebody opens this URL in a tab
|
|
598
|
+
// directly, where those scripts would run under THIS
|
|
599
|
+
// origin with this user's session — stored XSS for anyone who can write
|
|
600
|
+
// a file into the workspace. `sandbox` drops the document into a unique
|
|
601
|
+
// origin with scripting off. It applies to documents only, so the
|
|
602
|
+
// renderers' fetch → blob → <img> path is untouched, and that path is
|
|
603
|
+
// why the type stays `image/svg+xml`: browsers do not sniff SVG, and an
|
|
604
|
+
// octet-stream blob would simply not render.
|
|
605
|
+
if (!wantsDownload && ext === '.svg') {
|
|
606
|
+
res.setHeader('Content-Security-Policy', 'sandbox');
|
|
607
|
+
}
|
|
578
608
|
if (wantsDownload) {
|
|
579
609
|
// RFC 5987 UTF-8 filename encoding so unicode + spaces round-trip;
|
|
580
610
|
// CR/LF stripped to block header injection via a crafted path.
|
|
@@ -840,7 +870,7 @@ export function createWorkspaceRoutes(
|
|
|
840
870
|
res.status(400).json({ error: 'path query parameter is required' });
|
|
841
871
|
return;
|
|
842
872
|
}
|
|
843
|
-
const { content } = req.body as { content?: string };
|
|
873
|
+
const { content, ifAbsent } = req.body as { content?: string; ifAbsent?: boolean };
|
|
844
874
|
if (content === undefined) {
|
|
845
875
|
res.status(400).json({ error: 'content is required in body' });
|
|
846
876
|
return;
|
|
@@ -861,7 +891,13 @@ export function createWorkspaceRoutes(
|
|
|
861
891
|
const plan = await creatorAccess.planForCreate(id, user, filePath, 'file');
|
|
862
892
|
if (plan?.kind === 'seed-access-md') await seedCreatorAccessMd(id, user, plan);
|
|
863
893
|
const toWrite = plan?.kind === 'frontmatter' ? plan.apply(content) : content;
|
|
864
|
-
|
|
894
|
+
// `ifAbsent` = exclusive create: the service's `wx` write turns a
|
|
895
|
+
// concurrent or stale create against an existing file into a 409
|
|
896
|
+
// instead of a silent replace. `withLock`'s failure arm releases
|
|
897
|
+
// without committing, so the refusal leaves no trace.
|
|
898
|
+
await withLock(id, user, filePath, () =>
|
|
899
|
+
workspaceService.writeFile(id, filePath, toWrite, { failIfExists: ifAbsent === true }),
|
|
900
|
+
);
|
|
865
901
|
res.json({ status: 'written' });
|
|
866
902
|
} catch (err) {
|
|
867
903
|
sendError(res, err);
|
|
@@ -147,10 +147,25 @@ export class WorkspaceService implements IWorkspaceService {
|
|
|
147
147
|
|
|
148
148
|
constructor(
|
|
149
149
|
private readonly workspacesRoot: string,
|
|
150
|
-
|
|
150
|
+
/**
|
|
151
|
+
* The KB remote. A GETTER is read per-clone, so a repository supplied
|
|
152
|
+
* through the setup screen takes effect without restarting the process; a
|
|
153
|
+
* plain string is still accepted for callers that have nothing to vary.
|
|
154
|
+
*
|
|
155
|
+
* `kbDirName` stays a plain string on purpose — it is threaded into a
|
|
156
|
+
* dozen other services at construction, so making it live only here would
|
|
157
|
+
* buy an inconsistency rather than a feature.
|
|
158
|
+
*/
|
|
159
|
+
kbRepoUrl: string | (() => string),
|
|
151
160
|
private readonly kbDirName: string,
|
|
152
|
-
|
|
153
|
-
) {
|
|
161
|
+
gitUsername: string | (() => string) = 'x-access-token',
|
|
162
|
+
) {
|
|
163
|
+
this.kbRepoUrl = typeof kbRepoUrl === 'function' ? kbRepoUrl : () => kbRepoUrl;
|
|
164
|
+
this.gitUsername = typeof gitUsername === 'function' ? gitUsername : () => gitUsername;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
private readonly kbRepoUrl: () => string;
|
|
168
|
+
private readonly gitUsername: () => string;
|
|
154
169
|
|
|
155
170
|
setDiffService(diffService: IDiffService): void {
|
|
156
171
|
this.diffService = diffService;
|
|
@@ -185,10 +200,10 @@ export class WorkspaceService implements IWorkspaceService {
|
|
|
185
200
|
// Username is provider-specific (GitHub `x-access-token`, GitLab `oauth2`, …); the token is
|
|
186
201
|
// always the Basic-auth password, which every major host accepts.
|
|
187
202
|
args.push(
|
|
188
|
-
'-c', `credential.helper=!f() { echo "username=${this.gitUsername}"; echo "password=$GITHUB_TOKEN"; }; f`,
|
|
203
|
+
'-c', `credential.helper=!f() { echo "username=${this.gitUsername()}"; echo "password=$GITHUB_TOKEN"; }; f`,
|
|
189
204
|
);
|
|
190
205
|
}
|
|
191
|
-
args.push(this.kbRepoUrl, targetDir);
|
|
206
|
+
args.push(this.kbRepoUrl(), targetDir);
|
|
192
207
|
return args;
|
|
193
208
|
}
|
|
194
209
|
|
|
@@ -819,13 +834,36 @@ export class WorkspaceService implements IWorkspaceService {
|
|
|
819
834
|
}
|
|
820
835
|
}
|
|
821
836
|
|
|
822
|
-
async writeFile(
|
|
837
|
+
async writeFile(
|
|
838
|
+
workspaceId: string,
|
|
839
|
+
relativePath: string,
|
|
840
|
+
content: string,
|
|
841
|
+
options?: { failIfExists?: boolean },
|
|
842
|
+
): Promise<void> {
|
|
823
843
|
assertValidRelativePath(relativePath);
|
|
824
844
|
const workspaceDir = await this.resolveWorkspaceDir(workspaceId);
|
|
825
845
|
const absolutePath = path.resolve(workspaceDir, relativePath);
|
|
826
846
|
this.assertWithinWorkspace(absolutePath, workspaceDir);
|
|
827
847
|
await fs.mkdir(path.dirname(absolutePath), { recursive: true });
|
|
828
|
-
|
|
848
|
+
try {
|
|
849
|
+
// `wx` makes create-if-absent ATOMIC at the fs level — an exists-check
|
|
850
|
+
// followed by a plain write would let two concurrent creators (or a
|
|
851
|
+
// stale client whose file list predates the file) both pass the check
|
|
852
|
+
// and silently replace each other's content.
|
|
853
|
+
await fs.writeFile(absolutePath, content, {
|
|
854
|
+
encoding: 'utf-8',
|
|
855
|
+
flag: options?.failIfExists ? 'wx' : 'w',
|
|
856
|
+
});
|
|
857
|
+
} catch (err) {
|
|
858
|
+
if ((err as NodeJS.ErrnoException).code === 'EEXIST') {
|
|
859
|
+
const conflict: Error & { status?: number } = new Error(
|
|
860
|
+
`"${relativePath}" already exists.`,
|
|
861
|
+
);
|
|
862
|
+
conflict.status = 409;
|
|
863
|
+
throw conflict;
|
|
864
|
+
}
|
|
865
|
+
throw err;
|
|
866
|
+
}
|
|
829
867
|
await this.diffService?.syncFromDisk(workspaceId, relativePath);
|
|
830
868
|
}
|
|
831
869
|
|