@bevel-software/platform-core-backend 0.19.0 → 0.20.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/create-core-server.d.ts.map +1 -1
- package/dist/core/create-core-server.js +22 -0
- package/dist/core/create-core-server.js.map +1 -1
- package/dist/core/create-core-services.d.ts +3 -0
- package/dist/core/create-core-services.d.ts.map +1 -1
- package/dist/core/create-core-services.js +10 -1
- package/dist/core/create-core-services.js.map +1 -1
- package/dist/modules/audit/agent-audit.service.d.ts +54 -0
- package/dist/modules/audit/agent-audit.service.d.ts.map +1 -0
- package/dist/modules/audit/agent-audit.service.js +290 -0
- package/dist/modules/audit/agent-audit.service.js.map +1 -0
- package/dist/modules/audit/agent-rest-audit.middleware.d.ts +40 -0
- package/dist/modules/audit/agent-rest-audit.middleware.d.ts.map +1 -0
- package/dist/modules/audit/agent-rest-audit.middleware.js +75 -0
- package/dist/modules/audit/agent-rest-audit.middleware.js.map +1 -0
- package/dist/modules/audit/audit.contract.d.ts +145 -0
- package/dist/modules/audit/audit.contract.d.ts.map +1 -0
- package/dist/modules/audit/audit.contract.js +35 -0
- package/dist/modules/audit/audit.contract.js.map +1 -0
- package/dist/modules/audit/audit.routes.d.ts +21 -0
- package/dist/modules/audit/audit.routes.d.ts.map +1 -0
- package/dist/modules/audit/audit.routes.js +157 -0
- package/dist/modules/audit/audit.routes.js.map +1 -0
- package/dist/modules/audit/event-classifier.d.ts +52 -0
- package/dist/modules/audit/event-classifier.d.ts.map +1 -0
- package/dist/modules/audit/event-classifier.js +123 -0
- package/dist/modules/audit/event-classifier.js.map +1 -0
- package/dist/modules/audit/index.d.ts +7 -0
- package/dist/modules/audit/index.d.ts.map +1 -0
- package/dist/modules/audit/index.js +7 -0
- package/dist/modules/audit/index.js.map +1 -0
- package/dist/modules/audit/request-audit.d.ts +57 -0
- package/dist/modules/audit/request-audit.d.ts.map +1 -0
- package/dist/modules/audit/request-audit.js +117 -0
- package/dist/modules/audit/request-audit.js.map +1 -0
- package/dist/modules/database/core-schema.d.ts +414 -0
- package/dist/modules/database/core-schema.d.ts.map +1 -1
- package/dist/modules/database/core-schema.js +99 -0
- package/dist/modules/database/core-schema.js.map +1 -1
- package/dist/modules/diff/diff.service.d.ts.map +1 -1
- package/dist/modules/diff/diff.service.js +6 -1
- package/dist/modules/diff/diff.service.js.map +1 -1
- package/dist/modules/kb-fs/git-guarded-filesystem.d.ts +9 -1
- package/dist/modules/kb-fs/git-guarded-filesystem.d.ts.map +1 -1
- package/dist/modules/kb-fs/git-guarded-filesystem.js +16 -3
- package/dist/modules/kb-fs/git-guarded-filesystem.js.map +1 -1
- package/dist/modules/mcp/mcp-auth.middleware.d.ts +1 -0
- package/dist/modules/mcp/mcp-auth.middleware.d.ts.map +1 -1
- package/dist/modules/mcp/mcp-auth.middleware.js +32 -0
- package/dist/modules/mcp/mcp-auth.middleware.js.map +1 -1
- package/dist/modules/mcp/mcp.routes.d.ts.map +1 -1
- package/dist/modules/mcp/mcp.routes.js +8 -1
- package/dist/modules/mcp/mcp.routes.js.map +1 -1
- package/dist/modules/mcp/mcp.service.d.ts +9 -1
- package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
- package/dist/modules/mcp/mcp.service.js +48 -5
- package/dist/modules/mcp/mcp.service.js.map +1 -1
- package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts +59 -0
- package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts.map +1 -1
- package/dist/modules/mcp/oauth/bevel-oauth-provider.js +180 -8
- package/dist/modules/mcp/oauth/bevel-oauth-provider.js.map +1 -1
- package/dist/modules/settings/deployment-settings.service.d.ts +10 -2
- package/dist/modules/settings/deployment-settings.service.d.ts.map +1 -1
- package/dist/modules/settings/deployment-settings.service.js +32 -6
- package/dist/modules/settings/deployment-settings.service.js.map +1 -1
- package/dist/modules/tool-auth/external-api-key.interface.d.ts +21 -5
- package/dist/modules/tool-auth/external-api-key.interface.d.ts.map +1 -1
- package/dist/modules/tool-auth/external-api-key.interface.js.map +1 -1
- package/dist/modules/tool-auth/external-api-key.service.d.ts +1 -0
- package/dist/modules/tool-auth/external-api-key.service.d.ts.map +1 -1
- package/dist/modules/tool-auth/external-api-key.service.js +22 -9
- package/dist/modules/tool-auth/external-api-key.service.js.map +1 -1
- package/dist/modules/tool-auth/internal-token.service.d.ts +10 -0
- package/dist/modules/tool-auth/internal-token.service.d.ts.map +1 -1
- package/dist/modules/tool-auth/internal-token.service.js +1 -0
- package/dist/modules/tool-auth/internal-token.service.js.map +1 -1
- package/dist/modules/tool-auth/tool-auth.middleware.d.ts +8 -0
- package/dist/modules/tool-auth/tool-auth.middleware.d.ts.map +1 -1
- package/dist/modules/tool-auth/tool-auth.middleware.js +9 -1
- package/dist/modules/tool-auth/tool-auth.middleware.js.map +1 -1
- package/dist/modules/workspace/workspace.service.d.ts +8 -0
- package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.service.js +9 -0
- 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 +49 -13
- package/dist/modules/workspace/workspace.tools.js.map +1 -1
- package/dist/shared/git-internals.d.ts +7 -1
- package/dist/shared/git-internals.d.ts.map +1 -1
- package/dist/shared/git-internals.js +67 -9
- package/dist/shared/git-internals.js.map +1 -1
- package/migrations/0012_agent_audit.sql +41 -0
- package/migrations/0013_agent_identity_and_key_soft_delete.sql +29 -0
- package/migrations/meta/0012_snapshot.json +2247 -0
- package/migrations/meta/0013_snapshot.json +2259 -0
- package/migrations/meta/_journal.json +14 -0
- package/package.json +4 -4
- package/src/core/create-core-server.ts +28 -0
- package/src/core/create-core-services.ts +13 -0
- package/src/modules/audit/__tests__/agent-audit.service.test.ts +370 -0
- package/src/modules/audit/__tests__/agent-rest-audit.middleware.test.ts +113 -0
- package/src/modules/audit/__tests__/audit.routes.test.ts +201 -0
- package/src/modules/audit/__tests__/event-classifier.test.ts +121 -0
- package/src/modules/audit/agent-audit.service.ts +316 -0
- package/src/modules/audit/agent-rest-audit.middleware.ts +83 -0
- package/src/modules/audit/audit.contract.ts +164 -0
- package/src/modules/audit/audit.routes.ts +168 -0
- package/src/modules/audit/event-classifier.ts +155 -0
- package/src/modules/audit/index.ts +14 -0
- package/src/modules/audit/request-audit.ts +141 -0
- package/src/modules/database/core-schema.ts +104 -0
- package/src/modules/diff/diff.service.ts +6 -1
- package/src/modules/kb-fs/git-guarded-filesystem.ts +16 -3
- package/src/modules/mcp/__tests__/bevel-oauth-provider.test.ts +183 -13
- package/src/modules/mcp/__tests__/mcp-auth.middleware.test.ts +60 -2
- package/src/modules/mcp/__tests__/mcp.routes.local-token.test.ts +24 -4
- package/src/modules/mcp/__tests__/mcp.routes.stateless.test.ts +1 -1
- package/src/modules/mcp/__tests__/mcp.service.test.ts +120 -3
- package/src/modules/mcp/mcp-auth.middleware.ts +31 -0
- package/src/modules/mcp/mcp.routes.ts +11 -1
- package/src/modules/mcp/mcp.service.ts +62 -4
- package/src/modules/mcp/oauth/bevel-oauth-provider.ts +184 -6
- package/src/modules/settings/__tests__/deployment-settings.service.test.ts +35 -0
- package/src/modules/settings/deployment-settings.service.ts +47 -8
- package/src/modules/tool-auth/__tests__/external-api-key.service.test.ts +29 -8
- package/src/modules/tool-auth/__tests__/internal-token.service.test.ts +6 -0
- package/src/modules/tool-auth/external-api-key.interface.ts +22 -5
- package/src/modules/tool-auth/external-api-key.service.ts +25 -8
- package/src/modules/tool-auth/internal-token.service.ts +11 -0
- package/src/modules/tool-auth/tool-auth.middleware.ts +17 -1
- package/src/modules/workspace/__tests__/git-internals.security.test.ts +135 -44
- package/src/modules/workspace/workspace.service.ts +9 -0
- package/src/modules/workspace/workspace.tools.ts +47 -11
- package/src/shared/__tests__/git-internals.test.ts +41 -0
- package/src/shared/git-internals.ts +61 -6
- package/dist/modules/access/access-errors.d.ts +0 -34
- package/dist/modules/access/access-errors.d.ts.map +0 -1
- package/dist/modules/access/access-errors.js +0 -40
- package/dist/modules/access/access-errors.js.map +0 -1
- package/dist/modules/access/access-splice.d.ts +0 -140
- package/dist/modules/access/access-splice.d.ts.map +0 -1
- package/dist/modules/access/access-splice.js +0 -389
- package/dist/modules/access/access-splice.js.map +0 -1
- package/dist/modules/access/group-files.d.ts +0 -83
- package/dist/modules/access/group-files.d.ts.map +0 -1
- package/dist/modules/access/group-files.js +0 -167
- package/dist/modules/access/group-files.js.map +0 -1
- package/dist/modules/access/kb-read-filter.d.ts +0 -41
- package/dist/modules/access/kb-read-filter.d.ts.map +0 -1
- package/dist/modules/access/kb-read-filter.js +0 -60
- package/dist/modules/access/kb-read-filter.js.map +0 -1
- package/dist/modules/access/render-roles-yaml.d.ts +0 -22
- package/dist/modules/access/render-roles-yaml.d.ts.map +0 -1
- package/dist/modules/access/render-roles-yaml.js +0 -56
- package/dist/modules/access/render-roles-yaml.js.map +0 -1
- package/dist/modules/access/roles-yaml-guard.d.ts +0 -56
- package/dist/modules/access/roles-yaml-guard.d.ts.map +0 -1
- package/dist/modules/access/roles-yaml-guard.js +0 -79
- package/dist/modules/access/roles-yaml-guard.js.map +0 -1
- package/dist/modules/diff/diff-paths.d.ts +0 -2
- package/dist/modules/diff/diff-paths.d.ts.map +0 -1
- package/dist/modules/diff/diff-paths.js +0 -9
- package/dist/modules/diff/diff-paths.js.map +0 -1
- package/dist/modules/groups/group-provision.service.d.ts +0 -130
- package/dist/modules/groups/group-provision.service.d.ts.map +0 -1
- package/dist/modules/groups/group-provision.service.js +0 -288
- package/dist/modules/groups/group-provision.service.js.map +0 -1
- package/dist/modules/groups/groups.contract.d.ts +0 -106
- package/dist/modules/groups/groups.contract.d.ts.map +0 -1
- package/dist/modules/groups/groups.contract.js +0 -36
- package/dist/modules/groups/groups.contract.js.map +0 -1
- package/dist/modules/groups/groups.routes.d.ts +0 -42
- package/dist/modules/groups/groups.routes.d.ts.map +0 -1
- package/dist/modules/groups/groups.routes.js +0 -379
- package/dist/modules/groups/groups.routes.js.map +0 -1
- package/dist/modules/groups/groups.service.d.ts +0 -60
- package/dist/modules/groups/groups.service.d.ts.map +0 -1
- package/dist/modules/groups/groups.service.js +0 -172
- package/dist/modules/groups/groups.service.js.map +0 -1
- package/dist/modules/groups/index.d.ts +0 -7
- package/dist/modules/groups/index.d.ts.map +0 -1
- package/dist/modules/groups/index.js +0 -6
- package/dist/modules/groups/index.js.map +0 -1
- package/dist/modules/groups/join-proposals.d.ts +0 -53
- package/dist/modules/groups/join-proposals.d.ts.map +0 -1
- package/dist/modules/groups/join-proposals.js +0 -67
- package/dist/modules/groups/join-proposals.js.map +0 -1
- package/dist/modules/groups/join-requests.service.d.ts +0 -81
- package/dist/modules/groups/join-requests.service.d.ts.map +0 -1
- package/dist/modules/groups/join-requests.service.js +0 -135
- package/dist/modules/groups/join-requests.service.js.map +0 -1
- package/dist/modules/mcp/mcp-session-store.d.ts +0 -74
- package/dist/modules/mcp/mcp-session-store.d.ts.map +0 -1
- package/dist/modules/mcp/mcp-session-store.js +0 -131
- package/dist/modules/mcp/mcp-session-store.js.map +0 -1
- package/dist/modules/workflow/file-change-notifier.d.ts +0 -38
- package/dist/modules/workflow/file-change-notifier.d.ts.map +0 -1
- package/dist/modules/workflow/file-change-notifier.js +0 -22
- package/dist/modules/workflow/file-change-notifier.js.map +0 -1
- package/dist/modules/workflow/git/branch-name.d.ts +0 -10
- package/dist/modules/workflow/git/branch-name.d.ts.map +0 -1
- package/dist/modules/workflow/git/branch-name.js +0 -76
- package/dist/modules/workflow/git/branch-name.js.map +0 -1
- package/dist/modules/workflow/git/clone-config.d.ts +0 -61
- package/dist/modules/workflow/git/clone-config.d.ts.map +0 -1
- package/dist/modules/workflow/git/clone-config.js +0 -69
- package/dist/modules/workflow/git/clone-config.js.map +0 -1
- package/dist/modules/workflow/git/mutex.d.ts +0 -11
- package/dist/modules/workflow/git/mutex.d.ts.map +0 -1
- package/dist/modules/workflow/git/mutex.js +0 -23
- package/dist/modules/workflow/git/mutex.js.map +0 -1
- package/dist/modules/workflow/locking-filesystem.d.ts +0 -137
- package/dist/modules/workflow/locking-filesystem.d.ts.map +0 -1
- package/dist/modules/workflow/locking-filesystem.js +0 -553
- package/dist/modules/workflow/locking-filesystem.js.map +0 -1
- package/dist/modules/workflow/read-only-filesystem.d.ts +0 -24
- package/dist/modules/workflow/read-only-filesystem.d.ts.map +0 -1
- package/dist/modules/workflow/read-only-filesystem.js +0 -39
- package/dist/modules/workflow/read-only-filesystem.js.map +0 -1
- package/dist/modules/workflow/workflow.errors.d.ts +0 -197
- package/dist/modules/workflow/workflow.errors.d.ts.map +0 -1
- package/dist/modules/workflow/workflow.errors.js +0 -298
- package/dist/modules/workflow/workflow.errors.js.map +0 -1
- package/dist/modules/workspace/bevel-ignore.d.ts +0 -30
- package/dist/modules/workspace/bevel-ignore.d.ts.map +0 -1
- package/dist/modules/workspace/bevel-ignore.js +0 -61
- package/dist/modules/workspace/bevel-ignore.js.map +0 -1
- package/dist/modules/workspace/kb-seed.interface.d.ts +0 -36
- package/dist/modules/workspace/kb-seed.interface.d.ts.map +0 -1
- package/dist/modules/workspace/kb-seed.interface.js +0 -2
- package/dist/modules/workspace/kb-seed.interface.js.map +0 -1
- package/dist/modules/workspace/kb-seed.service.d.ts +0 -125
- package/dist/modules/workspace/kb-seed.service.d.ts.map +0 -1
- package/dist/modules/workspace/kb-seed.service.js +0 -535
- package/dist/modules/workspace/kb-seed.service.js.map +0 -1
- package/dist/modules/workspace/plugins-migration.d.ts +0 -50
- package/dist/modules/workspace/plugins-migration.d.ts.map +0 -1
- package/dist/modules/workspace/plugins-migration.js +0 -379
- package/dist/modules/workspace/plugins-migration.js.map +0 -1
- package/dist/shared/fs-errors.d.ts +0 -8
- package/dist/shared/fs-errors.d.ts.map +0 -1
- package/dist/shared/fs-errors.js +0 -11
- package/dist/shared/fs-errors.js.map +0 -1
- package/dist/shared/fs-walk.d.ts +0 -23
- package/dist/shared/fs-walk.d.ts.map +0 -1
- package/dist/shared/fs-walk.js +0 -42
- package/dist/shared/fs-walk.js.map +0 -1
- package/dist/shared/hash-email.d.ts +0 -11
- package/dist/shared/hash-email.d.ts.map +0 -1
- package/dist/shared/hash-email.js +0 -14
- package/dist/shared/hash-email.js.map +0 -1
- package/dist/shared/kb-walk.d.ts +0 -52
- package/dist/shared/kb-walk.d.ts.map +0 -1
- package/dist/shared/kb-walk.js +0 -76
- package/dist/shared/kb-walk.js.map +0 -1
|
@@ -1,288 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Group provisioning — the ONE privileged door for bringing a `Groups/<name>/`
|
|
3
|
-
* folder into existence, and (via {@link GroupProvisionService.deleteGroup})
|
|
4
|
-
* for taking one back out of it.
|
|
5
|
-
*
|
|
6
|
-
* Creating a group is the single operation the access model cannot govern
|
|
7
|
-
* from inside: the folder that will carry the rules does not exist yet, and
|
|
8
|
-
* the root's own rule (`write: Admin`) says no. The old answer was a
|
|
9
|
-
* carve-out inside the generic write gate — every write path could claim an
|
|
10
|
-
* unused name under `Groups/`, and the gate had to re-derive "is this that
|
|
11
|
-
* one blessed case?" on every check. This service replaces that: the generic
|
|
12
|
-
* gate is uniformly strict again, and the privilege lives here, named,
|
|
13
|
-
* behind its own endpoint.
|
|
14
|
-
*
|
|
15
|
-
* What a provision IS: one exclusive write of the new folder's `access.md`
|
|
16
|
-
* (flag `wx`, so two concurrent creators race for the fs, not for the
|
|
17
|
-
* overwrite), then one SYNCHRONOUS commit+push. Synchronous on purpose — the
|
|
18
|
-
* write gate reads rules at HEAD, so a creation whose access.md were still
|
|
19
|
-
* sitting in the async commit queue would 403 the very next thing its
|
|
20
|
-
* creator does (writing the first skill into it).
|
|
21
|
-
*
|
|
22
|
-
* Two shapes, two templates:
|
|
23
|
-
*
|
|
24
|
-
* - A NAMED group: discoverable by design. The access.md's own frontmatter
|
|
25
|
-
* reads `everyone` — anyone may open the FILE, see the group listed, and
|
|
26
|
-
* ask to join — while the BODY (the folder's actual rules) names only
|
|
27
|
-
* the creator under read, write and owner.
|
|
28
|
-
* - A PERSONAL folder (`personal-<user-id>`): private by design. No
|
|
29
|
-
* frontmatter grant at all, so nobody else can even see it exists; the
|
|
30
|
-
* body names its owner. Created lazily (ensure semantics) on the first
|
|
31
|
-
* personal skill.
|
|
32
|
-
*/
|
|
33
|
-
import fs from 'node:fs/promises';
|
|
34
|
-
import path from 'node:path';
|
|
35
|
-
import { DEFAULT_BRANCH, GROUPS_DIR, PERSONAL_GROUP_PREFIX, isPersonalGroupFolder, personalGroupFolderName, } from '@bevel-software/platform-shared';
|
|
36
|
-
import { creatorPrincipal } from '../access/creator-access.js';
|
|
37
|
-
import { spliceGrant } from '../access/access-splice.js';
|
|
38
|
-
import { WorkspaceMutex } from '../workflow/git/mutex.js';
|
|
39
|
-
/** A refusal the route can pass through: message + HTTP status. */
|
|
40
|
-
export class GroupProvisionError extends Error {
|
|
41
|
-
status;
|
|
42
|
-
constructor(message, status) {
|
|
43
|
-
super(message);
|
|
44
|
-
this.status = status;
|
|
45
|
-
this.name = 'GroupProvisionError';
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
export class GroupProvisionService {
|
|
49
|
-
workspaceService;
|
|
50
|
-
commits;
|
|
51
|
-
accessControl;
|
|
52
|
-
kbDirName;
|
|
53
|
-
events;
|
|
54
|
-
/**
|
|
55
|
-
* Serialises creations by NORMALIZED (lowercased) folder name. The wx write
|
|
56
|
-
* arbitrates same-path races, but on a case-sensitive filesystem `GTM` and
|
|
57
|
-
* `gtm` are different paths — without this lock two concurrent requests
|
|
58
|
-
* could both pass the collision check and both land, breaking the
|
|
59
|
-
* case-insensitive uniqueness the check promises.
|
|
60
|
-
*/
|
|
61
|
-
creations = new WorkspaceMutex();
|
|
62
|
-
constructor(workspaceService, commits, accessControl, kbDirName, events) {
|
|
63
|
-
this.workspaceService = workspaceService;
|
|
64
|
-
this.commits = commits;
|
|
65
|
-
this.accessControl = accessControl;
|
|
66
|
-
this.kbDirName = kbDirName;
|
|
67
|
-
this.events = events;
|
|
68
|
-
}
|
|
69
|
-
/**
|
|
70
|
-
* Create `Groups/<name>/` for `user`. Throws `GroupProvisionError` 422 on a
|
|
71
|
-
* name the filesystem or the model cannot carry, 409 when the name is taken
|
|
72
|
-
* (case-insensitively — the workspaces live on case-insensitive
|
|
73
|
-
* filesystems too, where `GTM` and `gtm` are one folder).
|
|
74
|
-
*/
|
|
75
|
-
async createGroup(user, rawName) {
|
|
76
|
-
const name = rawName.trim();
|
|
77
|
-
if (!name)
|
|
78
|
-
throw new GroupProvisionError('A group needs a name.', 422);
|
|
79
|
-
// eslint-disable-next-line no-control-regex -- NUL and control chars are
|
|
80
|
-
// exactly what a filesystem path cannot carry; refusing them here keeps
|
|
81
|
-
// the refusal a 422 instead of the fs layer's 500.
|
|
82
|
-
if (/[/\\\u0000-\u001f\u007f]/.test(name) || name === '.' || name === '..' || name.startsWith('.')) {
|
|
83
|
-
throw new GroupProvisionError('A group name can\'t contain / or \\ or control characters, or start with a dot.', 422);
|
|
84
|
-
}
|
|
85
|
-
if (name.toLowerCase().startsWith(PERSONAL_GROUP_PREFIX)) {
|
|
86
|
-
// Reserved: the personal-folder namespace. A group squatting there
|
|
87
|
-
// would collide with somebody's future personal folder.
|
|
88
|
-
throw new GroupProvisionError(`Group names starting with "${PERSONAL_GROUP_PREFIX}" are reserved.`, 422);
|
|
89
|
-
}
|
|
90
|
-
return this.creations.run(`group:${name.toLowerCase()}`, async () => {
|
|
91
|
-
const existing = await this.existingFolder(name);
|
|
92
|
-
if (existing !== null) {
|
|
93
|
-
throw new GroupProvisionError(`A group named "${existing}" already exists.`, 409);
|
|
94
|
-
}
|
|
95
|
-
await this.provision(user, name, groupAccessMd(user));
|
|
96
|
-
return { folder: name, created: true };
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
/**
|
|
100
|
-
* Ensure the caller's personal folder exists — idempotent, keyed to the
|
|
101
|
-
* stable user id. Returns `created: false` when it is already there.
|
|
102
|
-
*/
|
|
103
|
-
async ensurePersonalGroup(user) {
|
|
104
|
-
const folder = personalGroupFolderName(user.id);
|
|
105
|
-
return this.creations.run(`group:${folder.toLowerCase()}`, async () => {
|
|
106
|
-
if ((await this.existingFolder(folder)) !== null) {
|
|
107
|
-
return { folder, created: false };
|
|
108
|
-
}
|
|
109
|
-
try {
|
|
110
|
-
await this.provision(user, folder, personalAccessMd(user));
|
|
111
|
-
}
|
|
112
|
-
catch (err) {
|
|
113
|
-
// ENSURE semantics even under a race the lock cannot see (another
|
|
114
|
-
// process, a checkout that appeared between check and write): the
|
|
115
|
-
// folder existing is this method's success case, never its error.
|
|
116
|
-
if (err instanceof GroupProvisionError && err.status === 409) {
|
|
117
|
-
return { folder, created: false };
|
|
118
|
-
}
|
|
119
|
-
throw err;
|
|
120
|
-
}
|
|
121
|
-
return { folder, created: true };
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
/**
|
|
125
|
-
* Delete `Groups/<name>/` — the whole folder, its skills and tools
|
|
126
|
-
* included, in ONE commit. MECHANISM only: the route owns the
|
|
127
|
-
* authorization (the caller must hold the `owner` verb on the folder;
|
|
128
|
-
* this service never re-derives it), exactly as `createGroup` leaves
|
|
129
|
-
* "any signed-in user" to its endpoint.
|
|
130
|
-
*
|
|
131
|
-
* Shape mirrors a provision run in reverse, with the same failure
|
|
132
|
-
* contract: the folder is PARKED (renamed to a dot-prefixed sibling the
|
|
133
|
-
* scanners ignore) rather than removed, the deletion is committed
|
|
134
|
-
* synchronously, and only a landed commit lets the parked bytes go. A
|
|
135
|
-
* refused commit renames the folder back, so a failed delete leaves the
|
|
136
|
-
* group exactly as it was — never half-gone on disk while origin still
|
|
137
|
-
* carries it.
|
|
138
|
-
*
|
|
139
|
-
* Serialised on the same per-name lock creations use, so a delete can
|
|
140
|
-
* never interleave with a re-creation of the same name.
|
|
141
|
-
*/
|
|
142
|
-
async deleteGroup(user, rawName) {
|
|
143
|
-
const name = rawName.trim();
|
|
144
|
-
if (!name)
|
|
145
|
-
throw new GroupProvisionError('A group needs a name.', 422);
|
|
146
|
-
if (isPersonalGroupFolder(name)) {
|
|
147
|
-
// Personal folders are not groups (the catalog never lists them), and
|
|
148
|
-
// nobody deletes somebody's private shelf through the group door.
|
|
149
|
-
throw new GroupProvisionError('Unknown group', 404);
|
|
150
|
-
}
|
|
151
|
-
return this.creations.run(`group:${name.toLowerCase()}`, async () => {
|
|
152
|
-
const existing = await this.existingFolder(name);
|
|
153
|
-
// Exact match only — the catalog hands the route the on-disk casing,
|
|
154
|
-
// so a mismatch means the group is gone (or was never there).
|
|
155
|
-
if (existing !== name)
|
|
156
|
-
throw new GroupProvisionError('Unknown group', 404);
|
|
157
|
-
const wsId = await this.readyWorkspaceId();
|
|
158
|
-
const wsDir = await this.workspaceService.getWorkspacePath(wsId);
|
|
159
|
-
const groupsDir = path.join(wsDir, this.kbDirName, GROUPS_DIR);
|
|
160
|
-
const folderDir = path.join(groupsDir, name);
|
|
161
|
-
// Dot-prefixed ⇒ invisible to the group scanner and the collision
|
|
162
|
-
// check for the whole window the commit is in flight.
|
|
163
|
-
const parkedDir = path.join(groupsDir, `.${name}.deleting`);
|
|
164
|
-
await fs.rm(parkedDir, { recursive: true, force: true }); // a stale park from a crashed run
|
|
165
|
-
await fs.rename(folderDir, parkedDir);
|
|
166
|
-
try {
|
|
167
|
-
// Inline and `systemAuthorized`, for `provision`'s reasons in
|
|
168
|
-
// reverse: the gate reads rules at HEAD, and the endpoint has
|
|
169
|
-
// already authorized the delete (owner verdict), so the per-user
|
|
170
|
-
// push gate — which would re-read an access.md this very commit
|
|
171
|
-
// removes — is skipped for exactly this commit. `commitFile` is
|
|
172
|
-
// path-scoped (`git add -- <path>`), and a folder path stages every
|
|
173
|
-
// deletion under it: one commit, one removed group.
|
|
174
|
-
await this.commits.runPendingCommit(wsId, DEFAULT_BRANCH, `${this.kbDirName}/${GROUPS_DIR}/${name}`, user, { systemAuthorized: true });
|
|
175
|
-
}
|
|
176
|
-
catch (err) {
|
|
177
|
-
// The commit did not land: put the folder back, so a failed delete
|
|
178
|
-
// is a no-op rather than a group that exists at origin but not here.
|
|
179
|
-
try {
|
|
180
|
-
await fs.rename(parkedDir, folderDir);
|
|
181
|
-
}
|
|
182
|
-
catch {
|
|
183
|
-
/* the park survives for the next attempt — better than masking the real error */
|
|
184
|
-
}
|
|
185
|
-
throw err;
|
|
186
|
-
}
|
|
187
|
-
await fs.rm(parkedDir, { recursive: true, force: true }).catch(() => { });
|
|
188
|
-
// The folder's rules left the access model — drop the resolver cache
|
|
189
|
-
// so the very next check runs against a tree without them.
|
|
190
|
-
this.accessControl.invalidate(wsId);
|
|
191
|
-
this.events?.emit({ kind: 'fs-tree-changed', workspaceId: wsId, branch: DEFAULT_BRANCH });
|
|
192
|
-
});
|
|
193
|
-
}
|
|
194
|
-
/** The taken name (in its on-disk casing) colliding with `name`, or null. */
|
|
195
|
-
async existingFolder(name) {
|
|
196
|
-
const wsId = await this.readyWorkspaceId();
|
|
197
|
-
const wsDir = await this.workspaceService.getWorkspacePath(wsId);
|
|
198
|
-
let children;
|
|
199
|
-
try {
|
|
200
|
-
children = await fs.readdir(path.join(wsDir, this.kbDirName, GROUPS_DIR));
|
|
201
|
-
}
|
|
202
|
-
catch {
|
|
203
|
-
return null; // no Groups/ root yet — nothing can collide
|
|
204
|
-
}
|
|
205
|
-
const lower = name.toLowerCase();
|
|
206
|
-
return children.find((c) => c.toLowerCase() === lower) ?? null;
|
|
207
|
-
}
|
|
208
|
-
async provision(user, folder, accessMd) {
|
|
209
|
-
const wsId = await this.readyWorkspaceId();
|
|
210
|
-
const wsRelPath = `${this.kbDirName}/${GROUPS_DIR}/${folder}/access.md`;
|
|
211
|
-
try {
|
|
212
|
-
// Exclusive create — the fs is the arbiter of a same-name race, not
|
|
213
|
-
// the (stale-able) existence check above.
|
|
214
|
-
await this.workspaceService.writeFile(wsId, wsRelPath, accessMd, { failIfExists: true });
|
|
215
|
-
}
|
|
216
|
-
catch (err) {
|
|
217
|
-
if (err.status === 409) {
|
|
218
|
-
throw new GroupProvisionError(`A group named "${folder}" already exists.`, 409);
|
|
219
|
-
}
|
|
220
|
-
throw err;
|
|
221
|
-
}
|
|
222
|
-
try {
|
|
223
|
-
// Inline, not enqueued: the gate reads rules at HEAD, so the folder is
|
|
224
|
-
// only real once this commit lands. `runPendingCommit` is the same
|
|
225
|
-
// commit+push (with pull-rebase recovery) the queue worker runs.
|
|
226
|
-
// `systemAuthorized`: the push gate reads access at origin, where this
|
|
227
|
-
// folder does not exist yet and the root says `write: Admin` — the very
|
|
228
|
-
// rule this endpoint exists to carve through. The endpoint has already
|
|
229
|
-
// authorized the write (any signed-in user, unused name, exclusive
|
|
230
|
-
// create), so the per-user gate is skipped for exactly this commit.
|
|
231
|
-
await this.commits.runPendingCommit(wsId, DEFAULT_BRANCH, wsRelPath, user, {
|
|
232
|
-
systemAuthorized: true,
|
|
233
|
-
});
|
|
234
|
-
}
|
|
235
|
-
catch (err) {
|
|
236
|
-
// The commit did not land: roll the seeded file back off the disk,
|
|
237
|
-
// best-effort, so a retry doesn't find a half-made group and report
|
|
238
|
-
// "already exists" for something that never got committed. Only OUR
|
|
239
|
-
// file and — when that leaves it empty — the folder; never recursive,
|
|
240
|
-
// so a concurrent writer's bytes can't be collateral.
|
|
241
|
-
try {
|
|
242
|
-
const wsDir = await this.workspaceService.getWorkspacePath(wsId);
|
|
243
|
-
const folderDir = path.join(wsDir, this.kbDirName, GROUPS_DIR, folder);
|
|
244
|
-
await fs.rm(path.join(folderDir, 'access.md'), { force: true });
|
|
245
|
-
await fs.rmdir(folderDir).catch(() => { });
|
|
246
|
-
}
|
|
247
|
-
catch {
|
|
248
|
-
/* leave it for the next attempt's wx conflict — better than masking the real error */
|
|
249
|
-
}
|
|
250
|
-
throw err;
|
|
251
|
-
}
|
|
252
|
-
// The folder's rules changed the access model — drop the resolver cache
|
|
253
|
-
// so the very next check (the creator's first skill write) sees them.
|
|
254
|
-
this.accessControl.invalidate(wsId);
|
|
255
|
-
this.events?.emit({ kind: 'fs-tree-changed', workspaceId: wsId, branch: DEFAULT_BRANCH });
|
|
256
|
-
}
|
|
257
|
-
async readyWorkspaceId() {
|
|
258
|
-
const ws = await this.workspaceService.getOrCreateForBranch(DEFAULT_BRANCH);
|
|
259
|
-
return ws.id;
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
/**
|
|
263
|
-
* A named group's access.md: discoverable file (frontmatter `read: everyone`
|
|
264
|
-
* — anyone may see the group listed and ask to join), creator-run folder
|
|
265
|
-
* (body read/write/owner name the creator). The `read: []` placeholder makes
|
|
266
|
-
* the body parse as rules from the first byte, so every later splice targets
|
|
267
|
-
* the body rather than the frontmatter.
|
|
268
|
-
*/
|
|
269
|
-
export function groupAccessMd(creator) {
|
|
270
|
-
return withCreatorGrants('---\nread:\n - everyone\n---\nread: []\n', creator);
|
|
271
|
-
}
|
|
272
|
-
/**
|
|
273
|
-
* A personal folder's access.md: PRIVATE — no frontmatter grant, so the
|
|
274
|
-
* folder is invisible to everyone but its owner; the body names the owner
|
|
275
|
-
* under read, write and owner.
|
|
276
|
-
*/
|
|
277
|
-
export function personalAccessMd(creator) {
|
|
278
|
-
return withCreatorGrants('read: []\n', creator);
|
|
279
|
-
}
|
|
280
|
-
function withCreatorGrants(base, creator) {
|
|
281
|
-
const principal = creatorPrincipal(creator);
|
|
282
|
-
let out = base;
|
|
283
|
-
for (const verb of ['read', 'write', 'owner']) {
|
|
284
|
-
out = spliceGrant(out, verb, principal, { allowScalar: false, target: 'folder' }).text;
|
|
285
|
-
}
|
|
286
|
-
return out;
|
|
287
|
-
}
|
|
288
|
-
//# sourceMappingURL=group-provision.service.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"group-provision.service.js","sourceRoot":"","sources":["../../../src/modules/groups/group-provision.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Groups — the folders under `Groups/` that carry a team's skills and the
|
|
3
|
-
* tools those skills need. A group is a FOLDER, never a role: one access
|
|
4
|
-
* boundary, one place, derived from the path exactly the way `groupOfPath`
|
|
5
|
-
* derives it.
|
|
6
|
-
*
|
|
7
|
-
* A group EXISTS exactly when its folder carries an `access.md` — the file
|
|
8
|
-
* the provisioning endpoint seeds. A bare directory under `Groups/` is not a
|
|
9
|
-
* group (git cannot record an empty folder, so deleted groups leave ghost
|
|
10
|
-
* directories on live checkouts).
|
|
11
|
-
*
|
|
12
|
-
* Enumeration then follows the KB's read model with NO special cases: an
|
|
13
|
-
* existing group appears for a caller exactly when the access resolution says
|
|
14
|
-
* something about it —
|
|
15
|
-
*
|
|
16
|
-
* - MEMBER: the caller can read the folder itself (`canRead`).
|
|
17
|
-
* - MANAGER: the caller can write the folder's `access.md` (`canWrite`,
|
|
18
|
-
* admin-rescued).
|
|
19
|
-
* - DISCOVERABLE: the caller can read the `access.md` FILE — which, in the
|
|
20
|
-
* new body-governed format, is granted by the file's own
|
|
21
|
-
* `read: everyone` frontmatter (see `accessMdSelfEntries`). The group
|
|
22
|
-
* shows locked, and the caller may ask to join.
|
|
23
|
-
*
|
|
24
|
-
* A group where all three verdicts are false is ABSENT from the response —
|
|
25
|
-
* name, counts and principals never leave the backend. Making a group secret
|
|
26
|
-
* is therefore an ordinary access edit: drop the `everyone` grant from its
|
|
27
|
-
* access.md frontmatter.
|
|
28
|
-
*
|
|
29
|
-
* Requesting access is a CHANGE REQUEST, not a bespoke system: the join
|
|
30
|
-
* route commits "add me to the body's `read:` list" on a draft branch and
|
|
31
|
-
* opens a CR that the folder's writers approve by merging (or by granting
|
|
32
|
-
* via Manage access, which makes the CR moot). To agents these are plain
|
|
33
|
-
* change requests; only the UI dresses them up.
|
|
34
|
-
*/
|
|
35
|
-
/** Access-rule principals as the resolver hands them back. */
|
|
36
|
-
export interface ResolvedPrincipals {
|
|
37
|
-
roles: string[];
|
|
38
|
-
users: {
|
|
39
|
-
name: string;
|
|
40
|
-
email: string;
|
|
41
|
-
}[];
|
|
42
|
-
}
|
|
43
|
-
export interface ResolvedReaders extends ResolvedPrincipals {
|
|
44
|
-
restricted: boolean;
|
|
45
|
-
}
|
|
46
|
-
/** One group as `GET /api/groups` reports it, resolved for ONE caller. */
|
|
47
|
-
export interface GroupSummary {
|
|
48
|
-
/** Group folder name, e.g. `GTM`. */
|
|
49
|
-
name: string;
|
|
50
|
-
/** Repo-relative constituent folders, e.g. `['Groups/GTM']`. */
|
|
51
|
-
folders: string[];
|
|
52
|
-
/**
|
|
53
|
-
* Per-caller: the caller can read the FOLDER (membership). Every returned
|
|
54
|
-
* group has at least one of `canRead` / `canWrite` / discoverability; a
|
|
55
|
-
* group with none is not returned at all.
|
|
56
|
-
*/
|
|
57
|
-
canRead: boolean;
|
|
58
|
-
/**
|
|
59
|
-
* Per-caller; true ⇒ may manage access. Resolved as write on the folder's
|
|
60
|
-
* `access.md`, which admin-rescues — so a platform Admin locked OUT of
|
|
61
|
-
* reading still sees the group and gets the self-service way back in.
|
|
62
|
-
*/
|
|
63
|
-
canWrite: boolean;
|
|
64
|
-
/**
|
|
65
|
-
* Per-caller: the caller holds the `owner` verb on the FOLDER — resolved
|
|
66
|
-
* from the `owner:` lists alone, no admin rescue. Deleting the group is the
|
|
67
|
-
* owner's verb (the DELETE route enforces the same verdict); managers who
|
|
68
|
-
* merely write the access.md do not get it.
|
|
69
|
-
*/
|
|
70
|
-
isOwner: boolean;
|
|
71
|
-
/** Caller-INDEPENDENT total (the group's whole content, not the caller's slice). */
|
|
72
|
-
skillCount: number;
|
|
73
|
-
toolCount: number;
|
|
74
|
-
/** For display: "Run by …" (fallback chain: owners → writers → 'the workspace admins'). */
|
|
75
|
-
owners: ResolvedPrincipals;
|
|
76
|
-
writers: ResolvedPrincipals;
|
|
77
|
-
readers: ResolvedReaders;
|
|
78
|
-
/**
|
|
79
|
-
* The caller has an OPEN join change request for this group (their
|
|
80
|
-
* deterministic join branch has an open CR). Always false for a member.
|
|
81
|
-
*/
|
|
82
|
-
hasRequested: boolean;
|
|
83
|
-
/** The open join CR's number when `hasRequested` (deep-links the UI). */
|
|
84
|
-
requestNumber: number | null;
|
|
85
|
-
}
|
|
86
|
-
/**
|
|
87
|
-
* The caller-INDEPENDENT slice of a group — what `catalog()` computes once and
|
|
88
|
-
* caches. The per-caller verdicts (`canRead`/`canWrite`, and whether the group
|
|
89
|
-
* appears at all) are resolved per request in the route.
|
|
90
|
-
*/
|
|
91
|
-
export interface GroupCatalogEntry {
|
|
92
|
-
name: string;
|
|
93
|
-
folders: string[];
|
|
94
|
-
skillCount: number;
|
|
95
|
-
toolCount: number;
|
|
96
|
-
owners: ResolvedPrincipals;
|
|
97
|
-
writers: ResolvedPrincipals;
|
|
98
|
-
readers: ResolvedReaders;
|
|
99
|
-
}
|
|
100
|
-
export interface IGroupIndexService {
|
|
101
|
-
/** Caller-independent catalog part (folders, counts, principals) — cached 60s. */
|
|
102
|
-
catalog(): Promise<GroupCatalogEntry[]>;
|
|
103
|
-
/** Drop the cached catalog (call after a default-branch change under a group root). */
|
|
104
|
-
invalidate(): void;
|
|
105
|
-
}
|
|
106
|
-
//# sourceMappingURL=groups.contract.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"groups.contract.d.ts","sourceRoot":"","sources":["../../../src/modules/groups/groups.contract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,8DAA8D;AAC9D,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAC1C;AAED,MAAM,WAAW,eAAgB,SAAQ,kBAAkB;IACzD,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,0EAA0E;AAC1E,MAAM,WAAW,YAAY;IAC3B,qCAAqC;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,gEAAgE;IAChE,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB;;;;OAIG;IACH,OAAO,EAAE,OAAO,CAAC;IACjB;;;;OAIG;IACH,QAAQ,EAAE,OAAO,CAAC;IAClB;;;;;OAKG;IACH,OAAO,EAAE,OAAO,CAAC;IACjB,oFAAoF;IACpF,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,2FAA2F;IAC3F,MAAM,EAAE,kBAAkB,CAAC;IAC3B,OAAO,EAAE,kBAAkB,CAAC;IAC5B,OAAO,EAAE,eAAe,CAAC;IACzB;;;OAGG;IACH,YAAY,EAAE,OAAO,CAAC;IACtB,yEAAyE;IACzE,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAED;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,kBAAkB,CAAC;IAC3B,OAAO,EAAE,kBAAkB,CAAC;IAC5B,OAAO,EAAE,eAAe,CAAC;CAC1B;AAED,MAAM,WAAW,kBAAkB;IACjC,kFAAkF;IAClF,OAAO,IAAI,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAAC;IACxC,uFAAuF;IACvF,UAAU,IAAI,IAAI,CAAC;CACpB"}
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Groups — the folders under `Groups/` that carry a team's skills and the
|
|
3
|
-
* tools those skills need. A group is a FOLDER, never a role: one access
|
|
4
|
-
* boundary, one place, derived from the path exactly the way `groupOfPath`
|
|
5
|
-
* derives it.
|
|
6
|
-
*
|
|
7
|
-
* A group EXISTS exactly when its folder carries an `access.md` — the file
|
|
8
|
-
* the provisioning endpoint seeds. A bare directory under `Groups/` is not a
|
|
9
|
-
* group (git cannot record an empty folder, so deleted groups leave ghost
|
|
10
|
-
* directories on live checkouts).
|
|
11
|
-
*
|
|
12
|
-
* Enumeration then follows the KB's read model with NO special cases: an
|
|
13
|
-
* existing group appears for a caller exactly when the access resolution says
|
|
14
|
-
* something about it —
|
|
15
|
-
*
|
|
16
|
-
* - MEMBER: the caller can read the folder itself (`canRead`).
|
|
17
|
-
* - MANAGER: the caller can write the folder's `access.md` (`canWrite`,
|
|
18
|
-
* admin-rescued).
|
|
19
|
-
* - DISCOVERABLE: the caller can read the `access.md` FILE — which, in the
|
|
20
|
-
* new body-governed format, is granted by the file's own
|
|
21
|
-
* `read: everyone` frontmatter (see `accessMdSelfEntries`). The group
|
|
22
|
-
* shows locked, and the caller may ask to join.
|
|
23
|
-
*
|
|
24
|
-
* A group where all three verdicts are false is ABSENT from the response —
|
|
25
|
-
* name, counts and principals never leave the backend. Making a group secret
|
|
26
|
-
* is therefore an ordinary access edit: drop the `everyone` grant from its
|
|
27
|
-
* access.md frontmatter.
|
|
28
|
-
*
|
|
29
|
-
* Requesting access is a CHANGE REQUEST, not a bespoke system: the join
|
|
30
|
-
* route commits "add me to the body's `read:` list" on a draft branch and
|
|
31
|
-
* opens a CR that the folder's writers approve by merging (or by granting
|
|
32
|
-
* via Manage access, which makes the CR moot). To agents these are plain
|
|
33
|
-
* change requests; only the UI dresses them up.
|
|
34
|
-
*/
|
|
35
|
-
export {};
|
|
36
|
-
//# sourceMappingURL=groups.contract.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"groups.contract.js","sourceRoot":"","sources":["../../../src/modules/groups/groups.contract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG"}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import express from 'express';
|
|
2
|
-
import '../auth/auth.middleware.js';
|
|
3
|
-
import { type AuthUser, type IWorkflowService } from '@bevel-software/platform-shared';
|
|
4
|
-
import type { IAccessControl } from '../access/access-control.interface.js';
|
|
5
|
-
import type { WorkspaceService } from '../workspace/workspace.service.js';
|
|
6
|
-
import { type GroupProvisionService } from './group-provision.service.js';
|
|
7
|
-
import type { JoinRequestsService } from './join-requests.service.js';
|
|
8
|
-
import type { IGroupIndexService } from './groups.contract.js';
|
|
9
|
-
/**
|
|
10
|
-
* Browser-facing (JWT) group routes, mounted behind `authMiddleware`:
|
|
11
|
-
*
|
|
12
|
-
* GET /api/groups → { groups: GroupSummary[] }
|
|
13
|
-
* DELETE /api/groups/:name → { ok } (owners)
|
|
14
|
-
* POST /api/groups/:name/join-request → { ok, number } (opens a CR)
|
|
15
|
-
* GET /api/groups/:name/join-requests → { requests } (managers)
|
|
16
|
-
* POST /api/groups/:name/join-requests/:n/reconcile → { closed }
|
|
17
|
-
*
|
|
18
|
-
* Enumeration is three verdicts per (caller, group), all of them ordinary
|
|
19
|
-
* access resolution — no special cases, no side tables:
|
|
20
|
-
*
|
|
21
|
-
* member canRead on the group FOLDER
|
|
22
|
-
* manager canWrite on the folder's access.md (admin-rescued)
|
|
23
|
-
* discoverable canRead on the access.md FILE — in the body-governed format
|
|
24
|
-
* its own `read: everyone` frontmatter grants this
|
|
25
|
-
*
|
|
26
|
-
* All three false ⇒ the group is absent from the response entirely.
|
|
27
|
-
*
|
|
28
|
-
* A join request is a plain change request whose branch edits the group's
|
|
29
|
-
* `access.md`. Managers do NOT merge it: they read its individual proposals
|
|
30
|
-
* (see `join-proposals.ts`), grant the ones they accept through the ordinary
|
|
31
|
-
* access path, and the request retires itself once its rules are a subset of
|
|
32
|
-
* the default branch's — reconciled here, lazily on listing and eagerly right
|
|
33
|
-
* after a grant. Nothing about the lifecycle is stored; it is derived from
|
|
34
|
-
* two copies of one file.
|
|
35
|
-
*
|
|
36
|
-
* Auth gating is explicit and uniform: `authMiddleware` at the mount, PLUS a
|
|
37
|
-
* `req.userEmail` check in every handler (the skills-routes pattern) so a
|
|
38
|
-
* middleware change can never silently un-gate one. Nothing here is reachable
|
|
39
|
-
* with an agent connection key or a manual-auth bearer.
|
|
40
|
-
*/
|
|
41
|
-
export declare function createGroupsRoutes(groupIndex: IGroupIndexService, accessControl: IAccessControl, workflow: IWorkflowService, workspaceService: WorkspaceService, joinRequests: JoinRequestsService, provision: GroupProvisionService, kbDirName: string, resolveUser: (req: express.Request) => Promise<AuthUser | null>): express.Router;
|
|
42
|
-
//# sourceMappingURL=groups.routes.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"groups.routes.d.ts","sourceRoot":"","sources":["../../../src/modules/groups/groups.routes.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,SAAS,CAAC;AAC9B,OAAO,4BAA4B,CAAC;AACpC,OAAO,EAGL,KAAK,QAAQ,EAEb,KAAK,gBAAgB,EACtB,MAAM,iCAAiC,CAAC;AACzC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAC;AAG5E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AAE1E,OAAO,EAAuB,KAAK,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAC/F,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACtE,OAAO,KAAK,EAGV,kBAAkB,EACnB,MAAM,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,kBAAkB,EAC9B,aAAa,EAAE,cAAc,EAC7B,QAAQ,EAAE,gBAAgB,EAC1B,gBAAgB,EAAE,gBAAgB,EAClC,YAAY,EAAE,mBAAmB,EACjC,SAAS,EAAE,qBAAqB,EAChC,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,CAAC,GAAG,EAAE,OAAO,CAAC,OAAO,KAAK,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,GAC9D,OAAO,CAAC,MAAM,CAiXhB"}
|