@bevel-software/platform-core-backend 0.28.0 → 0.29.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/agent-guide/access-control.md +15 -0
- package/agent-guide/skills.md +8 -0
- package/dist/core/catalog-revision.d.ts.map +1 -1
- package/dist/core/catalog-revision.js +4 -2
- package/dist/core/catalog-revision.js.map +1 -1
- package/dist/core/compose-embed.d.ts +35 -0
- package/dist/core/compose-embed.d.ts.map +1 -0
- package/dist/core/compose-embed.js +14 -0
- package/dist/core/compose-embed.js.map +1 -0
- package/dist/core/core-ports.d.ts +10 -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 +7 -1
- package/dist/core/create-core-server.js.map +1 -1
- package/dist/core/create-core-services.d.ts +1 -1
- package/dist/core/create-core-services.d.ts.map +1 -1
- package/dist/core/create-core-services.js +25 -14
- package/dist/core/create-core-services.js.map +1 -1
- package/dist/core/lifecycle.d.ts +11 -6
- package/dist/core/lifecycle.d.ts.map +1 -1
- package/dist/core/lifecycle.js +13 -6
- package/dist/core/lifecycle.js.map +1 -1
- package/dist/modules/access/access-control.interface.d.ts +5 -2
- package/dist/modules/access/access-control.interface.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.d.ts +36 -14
- package/dist/modules/access/access-control.service.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.js +208 -85
- package/dist/modules/access/access-control.service.js.map +1 -1
- package/dist/modules/auth/index.d.ts +15 -0
- package/dist/modules/auth/index.d.ts.map +1 -0
- package/dist/modules/auth/index.js +20 -0
- package/dist/modules/auth/index.js.map +1 -0
- package/dist/modules/embed/embed.interface.d.ts +22 -0
- package/dist/modules/embed/embed.interface.d.ts.map +1 -1
- package/dist/modules/embed/embed.service.d.ts +28 -14
- package/dist/modules/embed/embed.service.d.ts.map +1 -1
- package/dist/modules/embed/embed.service.js +38 -17
- package/dist/modules/embed/embed.service.js.map +1 -1
- package/dist/modules/embed/embed.tools.d.ts.map +1 -1
- package/dist/modules/embed/embed.tools.js +10 -2
- package/dist/modules/embed/embed.tools.js.map +1 -1
- package/dist/modules/kb-fs/index.d.ts +7 -0
- package/dist/modules/kb-fs/index.d.ts.map +1 -0
- package/dist/modules/kb-fs/index.js +2 -0
- package/dist/modules/kb-fs/index.js.map +1 -0
- package/dist/modules/plugins/join-request-jobs.service.d.ts.map +1 -1
- package/dist/modules/plugins/join-request-jobs.service.js +43 -8
- package/dist/modules/plugins/join-request-jobs.service.js.map +1 -1
- package/dist/modules/skills/skills.contract.d.ts.map +1 -1
- package/dist/modules/skills/skills.tools.d.ts +8 -1
- package/dist/modules/skills/skills.tools.d.ts.map +1 -1
- package/dist/modules/skills/skills.tools.js +115 -11
- package/dist/modules/skills/skills.tools.js.map +1 -1
- package/dist/modules/tool-helpers/default-branch-write.d.ts +69 -0
- package/dist/modules/tool-helpers/default-branch-write.d.ts.map +1 -0
- package/dist/modules/tool-helpers/default-branch-write.js +63 -0
- package/dist/modules/tool-helpers/default-branch-write.js.map +1 -0
- package/dist/modules/tool-helpers/index.d.ts +1 -0
- package/dist/modules/tool-helpers/index.d.ts.map +1 -1
- package/dist/modules/tool-helpers/index.js +1 -0
- package/dist/modules/tool-helpers/index.js.map +1 -1
- package/dist/modules/workflow/file-lock.service.d.ts +27 -5
- package/dist/modules/workflow/file-lock.service.d.ts.map +1 -1
- package/dist/modules/workflow/file-lock.service.js +70 -18
- package/dist/modules/workflow/file-lock.service.js.map +1 -1
- package/dist/modules/workflow/git/git.service.d.ts +13 -0
- package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
- package/dist/modules/workflow/git/git.service.js +24 -0
- package/dist/modules/workflow/git/git.service.js.map +1 -1
- package/dist/modules/workflow/index.d.ts +8 -0
- package/dist/modules/workflow/index.d.ts.map +1 -0
- package/dist/modules/workflow/index.js +8 -0
- package/dist/modules/workflow/index.js.map +1 -0
- package/dist/modules/workflow/pending-commits.service.d.ts +2 -17
- package/dist/modules/workflow/pending-commits.service.d.ts.map +1 -1
- package/dist/modules/workflow/pending-commits.service.js +4 -24
- package/dist/modules/workflow/pending-commits.service.js.map +1 -1
- package/dist/modules/workflow/workflow.routes.d.ts.map +1 -1
- package/dist/modules/workflow/workflow.routes.js +20 -1
- package/dist/modules/workflow/workflow.routes.js.map +1 -1
- package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
- package/dist/modules/workflow/workflow.service.js +5 -3
- package/dist/modules/workflow/workflow.service.js.map +1 -1
- package/dist/modules/workspace/index.d.ts +10 -0
- package/dist/modules/workspace/index.d.ts.map +1 -0
- package/dist/modules/workspace/index.js +10 -0
- package/dist/modules/workspace/index.js.map +1 -0
- package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.tools.js +106 -20
- package/dist/modules/workspace/workspace.tools.js.map +1 -1
- package/dist/shared/workspace-id.d.ts +17 -0
- package/dist/shared/workspace-id.d.ts.map +1 -1
- package/dist/shared/workspace-id.js +24 -0
- package/dist/shared/workspace-id.js.map +1 -1
- package/kb-template/access.md +2 -1
- package/mcp-app/page.html +142 -8
- package/package.json +3 -3
- package/src/core/__tests__/compose-embed.test.ts +65 -0
- package/src/core/__tests__/lifecycle.test.ts +29 -26
- package/src/core/catalog-revision.ts +4 -2
- package/src/core/compose-embed.ts +50 -0
- package/src/core/core-ports.ts +10 -0
- package/src/core/create-core-server.ts +7 -1
- package/src/core/create-core-services.ts +27 -21
- package/src/core/lifecycle.ts +24 -12
- package/src/modules/access/__tests__/access-control.admin-root-floor.test.ts +23 -3
- package/src/modules/access/__tests__/access-control.atref-cache.test.ts +303 -25
- package/src/modules/access/__tests__/access-control.download-read.test.ts +3 -1
- package/src/modules/access/__tests__/access-control.service.test.ts +77 -16
- package/src/modules/access/__tests__/change-read-gate.test.ts +36 -5
- package/src/modules/access/access-control.interface.ts +5 -2
- package/src/modules/access/access-control.service.ts +236 -95
- package/src/modules/auth/index.ts +29 -0
- package/src/modules/embed/__tests__/embed.service.test.ts +76 -3
- package/src/modules/embed/__tests__/embed.tools.test.ts +29 -0
- package/src/modules/embed/embed.interface.ts +24 -0
- package/src/modules/embed/embed.service.ts +44 -19
- package/src/modules/embed/embed.tools.ts +10 -2
- package/src/modules/kb-fs/index.ts +6 -0
- package/src/modules/plugins/__tests__/join-request-jobs.service.test.ts +73 -6
- package/src/modules/plugins/join-request-jobs.service.ts +45 -8
- package/src/modules/skills/__tests__/branch-skills.tools.test.ts +1 -1
- package/src/modules/skills/skills.contract.ts +4 -0
- package/src/modules/skills/skills.tools.ts +120 -10
- package/src/modules/tool-helpers/__tests__/can-write.tools.test.ts +581 -0
- package/src/modules/tool-helpers/default-branch-write.ts +90 -0
- package/src/modules/tool-helpers/index.ts +5 -0
- package/src/modules/tool-registry/__tests__/tool-description-length.test.ts +36 -2
- package/src/modules/workflow/__tests__/fake-file-lock-db.ts +13 -2
- package/src/modules/workflow/__tests__/workflow.routes.lock-path.test.ts +168 -1
- package/src/modules/workflow/__tests__/workflow.service.delete-branch.test.ts +102 -6
- package/src/modules/workflow/file-lock.service.ts +79 -18
- package/src/modules/workflow/git/__tests__/git.service.readFileAtRef.test.ts +42 -0
- package/src/modules/workflow/git/git.service.ts +23 -0
- package/src/modules/workflow/index.ts +7 -0
- package/src/modules/workflow/pending-commits.service.ts +4 -23
- package/src/modules/workflow/workflow.routes.ts +24 -1
- package/src/modules/workflow/workflow.service.ts +3 -2
- package/src/modules/workspace/__tests__/workspace.tools.test.ts +21 -11
- package/src/modules/workspace/index.ts +14 -0
- package/src/modules/workspace/workspace.tools.ts +120 -20
- package/src/shared/workspace-id.ts +24 -0
|
@@ -943,16 +943,24 @@ describe('AccessControlService', () => {
|
|
|
943
943
|
expect(await svc.canRead(workspaceId, 'sara@example.com', 'Knowledge/Foo.md')).toBe(true);
|
|
944
944
|
});
|
|
945
945
|
|
|
946
|
-
it('
|
|
946
|
+
it('the root floor gives admins read (write implies it) whatever the root says; a nearer scope can still exclude them', async () => {
|
|
947
947
|
const { workspaceDir, repo } = await seedWorkspace(root, workspaceId);
|
|
948
948
|
await writeFile(repo, 'roles.yaml', ROLES_YAML);
|
|
949
|
-
// Write/read restricted to Product Manager — Admin is
|
|
950
|
-
//
|
|
949
|
+
// Write/read restricted to Product Manager — Admin is not named at the
|
|
950
|
+
// root, but the floor gives Admin write there and write implies read, so
|
|
951
|
+
// an admin who could create anything under the root can also see it.
|
|
951
952
|
await writeFile(repo, 'access.md', '---\nwrite:\n - Product Manager\nread:\n - Product Manager\n---\n');
|
|
953
|
+
// A subfolder is an ordinary scope: it takes Admin's read away as it would anyone's.
|
|
954
|
+
await writeFile(repo, 'Knowledge/Board/access.md', '---\nread:\n - deny Admin\n---\n');
|
|
952
955
|
|
|
953
956
|
const svc = new AccessControlService(stubWorkspaceService(workspaceId, workspaceDir), PROCESS_MAP_DIR, new NodeFs());
|
|
954
|
-
// razvan is Admin but not a Product Manager
|
|
955
|
-
expect(await svc.canRead(workspaceId, 'razvan@bevel.software', 'Knowledge/Foo.md')).toBe(
|
|
957
|
+
// razvan is Admin but not a Product Manager.
|
|
958
|
+
expect(await svc.canRead(workspaceId, 'razvan@bevel.software', 'Knowledge/Foo.md')).toBe(true);
|
|
959
|
+
expect(await svc.canRead(workspaceId, 'razvan@bevel.software', 'Knowledge/Board/Minutes.md')).toBe(false);
|
|
960
|
+
// The floor is write and read, not download.
|
|
961
|
+
expect(await svc.canDownload(workspaceId, 'razvan@bevel.software', 'Knowledge/Foo.md')).toBe(false);
|
|
962
|
+
// Nobody else gains anything from it.
|
|
963
|
+
expect(await svc.canRead(workspaceId, 'ali@bevel.software', 'Knowledge/Foo.md')).toBe(false);
|
|
956
964
|
});
|
|
957
965
|
|
|
958
966
|
it("a node's own read: frontmatter grants just that file", async () => {
|
|
@@ -1002,17 +1010,24 @@ describe('AccessControlService', () => {
|
|
|
1002
1010
|
});
|
|
1003
1011
|
|
|
1004
1012
|
describe('eligibleReaders', () => {
|
|
1005
|
-
it('reports restricted=true with
|
|
1013
|
+
it('reports restricted=true, with Admin as the one reader, for a default-denied node', async () => {
|
|
1006
1014
|
const { workspaceDir, repo } = await seedWorkspace(root, workspaceId);
|
|
1007
1015
|
await writeFile(repo, 'roles.yaml', ROLES_YAML);
|
|
1008
1016
|
// The only line in the tree is a DENIAL — a denial grants nobody, and
|
|
1009
1017
|
// no verb that folds into read grants anyone either, so the node is
|
|
1010
|
-
// default-denied
|
|
1018
|
+
// default-denied. Admin still reads it through the root floor, and the
|
|
1019
|
+
// list says so: the banner must name who can actually open it.
|
|
1011
1020
|
await writeFile(repo, 'access.md', '---\nread:\n - deny everyone\n---\n');
|
|
1012
1021
|
|
|
1013
1022
|
const svc = new AccessControlService(stubWorkspaceService(workspaceId, workspaceDir), PROCESS_MAP_DIR, new NodeFs());
|
|
1014
1023
|
const e = await svc.eligibleReaders(workspaceId, 'Knowledge/Foo.md');
|
|
1015
|
-
expect(e).toEqual({
|
|
1024
|
+
expect(e).toEqual({
|
|
1025
|
+
restricted: true,
|
|
1026
|
+
principals: [{ name: 'Admin', kind: 'role' }],
|
|
1027
|
+
roles: ['Admin'],
|
|
1028
|
+
users: [],
|
|
1029
|
+
publicVia: [],
|
|
1030
|
+
});
|
|
1016
1031
|
});
|
|
1017
1032
|
|
|
1018
1033
|
it('reports restricted=false when read: everyone applies', async () => {
|
|
@@ -1047,10 +1062,15 @@ describe('AccessControlService', () => {
|
|
|
1047
1062
|
// node really is readable by everyone — not restricted.
|
|
1048
1063
|
expect(await svc.canRead(workspaceId, 'felix@example.com', 'Knowledge/Open/Foo.md')).toBe(true);
|
|
1049
1064
|
const e = await svc.eligibleReaders(workspaceId, 'Knowledge/Open/Foo.md');
|
|
1065
|
+
// Admin is listed beside everyone, as it is for write: the floor holds
|
|
1066
|
+
// wherever no nearer scope excludes it.
|
|
1050
1067
|
expect(e).toEqual({
|
|
1051
1068
|
restricted: false,
|
|
1052
|
-
principals: [
|
|
1053
|
-
|
|
1069
|
+
principals: [
|
|
1070
|
+
{ name: 'Admin', kind: 'role' },
|
|
1071
|
+
{ name: 'everyone', kind: 'role' },
|
|
1072
|
+
],
|
|
1073
|
+
roles: ['Admin', 'everyone'],
|
|
1054
1074
|
users: [],
|
|
1055
1075
|
publicVia: [],
|
|
1056
1076
|
});
|
|
@@ -1133,19 +1153,60 @@ describe('AccessControlService', () => {
|
|
|
1133
1153
|
});
|
|
1134
1154
|
|
|
1135
1155
|
/**
|
|
1136
|
-
* Beyond the two rescue files, the owner
|
|
1137
|
-
*
|
|
1138
|
-
* and
|
|
1156
|
+
* Beyond the two rescue files, the owner IS the Admin role: the root floor
|
|
1157
|
+
* (write, and the read it implies), every grant and denial that names
|
|
1158
|
+
* Admin along the way, and nothing more. An owner the file does not name
|
|
1159
|
+
* used to hold write everywhere and read nowhere, so the read-before-write
|
|
1160
|
+
* gate refused them the very file the floor let them create.
|
|
1139
1161
|
*/
|
|
1140
|
-
it('
|
|
1162
|
+
it('is bound by the Admin rules for every verb, as a member of the role would be', async () => {
|
|
1141
1163
|
const ws = await seeded();
|
|
1142
1164
|
const { repo } = await seedWorkspace(root, workspaceId);
|
|
1143
1165
|
await writeFile(repo, 'HR/access.md', '---\nwrite:\n - deny Admin\n---\n');
|
|
1166
|
+
await writeFile(repo, 'Board/access.md', '---\nread:\n - deny Admin\n---\n');
|
|
1167
|
+
await writeFile(repo, 'Vault/access.md', '---\ndownload:\n - Admin\n---\n');
|
|
1144
1168
|
const svc = new AccessControlService(ws, PROCESS_MAP_DIR, new NodeFs(), [OWNER]);
|
|
1169
|
+
// The floor: write and read under the root, not download.
|
|
1145
1170
|
expect(await svc.canWrite(workspaceId, OWNER, 'Knowledge/Foo.md')).toBe(true);
|
|
1146
|
-
expect(await svc.
|
|
1147
|
-
expect(await svc.canRead(workspaceId, OWNER, 'Knowledge/Foo.md')).toBe(false);
|
|
1171
|
+
expect(await svc.canRead(workspaceId, OWNER, 'Knowledge/Foo.md')).toBe(true);
|
|
1148
1172
|
expect(await svc.canDownload(workspaceId, OWNER, 'Knowledge/Foo.md')).toBe(false);
|
|
1173
|
+
// A nearer rule naming Admin binds the owner too, each verb on its own.
|
|
1174
|
+
expect(await svc.canWrite(workspaceId, OWNER, 'HR/Salaries.md')).toBe(false);
|
|
1175
|
+
expect(await svc.canRead(workspaceId, OWNER, 'HR/Salaries.md')).toBe(true);
|
|
1176
|
+
expect(await svc.canRead(workspaceId, OWNER, 'Board/Minutes.md')).toBe(false);
|
|
1177
|
+
expect(await svc.canWrite(workspaceId, OWNER, 'Board/Minutes.md')).toBe(false);
|
|
1178
|
+
// A grant to Admin is a grant to the owner — the role's keys are theirs.
|
|
1179
|
+
expect(await svc.canDownload(workspaceId, OWNER, 'Vault/Keys.md')).toBe(true);
|
|
1180
|
+
// The explorer's batch asks the same question and gets the same answer.
|
|
1181
|
+
const batch = await svc.canReadBatch(workspaceId, OWNER, ['Knowledge/Foo.md', 'Board/Minutes.md']);
|
|
1182
|
+
expect([...batch.entries()]).toEqual([
|
|
1183
|
+
['Knowledge/Foo.md', true],
|
|
1184
|
+
['Board/Minutes.md', false],
|
|
1185
|
+
]);
|
|
1186
|
+
});
|
|
1187
|
+
|
|
1188
|
+
it('is counted when the share dialog asks whether everyone can read: a public folder that denies Admin is restricted for them', async () => {
|
|
1189
|
+
const ws = await seeded();
|
|
1190
|
+
const { repo } = await seedWorkspace(root, workspaceId);
|
|
1191
|
+
// Everyone reads, Admin is carved out, and the role's one member is let
|
|
1192
|
+
// back in by name (a direct entry outranks the role's denial). The only
|
|
1193
|
+
// person the denial still reaches is the owner, whom roles.yaml does not
|
|
1194
|
+
// name — so a check that enumerates roles.yaml alone sees nobody denied.
|
|
1195
|
+
await writeFile(
|
|
1196
|
+
repo,
|
|
1197
|
+
'Open/access.md',
|
|
1198
|
+
'---\nread:\n - everyone\n - deny Admin\n - Someone Else <someone-else@example.com>\n---\n',
|
|
1199
|
+
);
|
|
1200
|
+
await writeFile(repo, 'Public/access.md', '---\nread:\n - everyone\n---\n');
|
|
1201
|
+
const svc = new AccessControlService(ws, PROCESS_MAP_DIR, new NodeFs(), [OWNER]);
|
|
1202
|
+
expect(await svc.canRead(workspaceId, 'someone-else@example.com', 'Open/Notes.md')).toBe(true);
|
|
1203
|
+
// The denial reaches the owner, so the folder is not readable by
|
|
1204
|
+
// everyone and the dialog must not say it is.
|
|
1205
|
+
expect(await svc.canRead(workspaceId, OWNER, 'Open/Notes.md')).toBe(false);
|
|
1206
|
+
expect((await svc.eligibleReaders(workspaceId, 'Open/Notes.md')).restricted).toBe(true);
|
|
1207
|
+
// Without the carve-out the same owner reads, and the folder is public.
|
|
1208
|
+
expect(await svc.canRead(workspaceId, OWNER, 'Public/Notes.md')).toBe(true);
|
|
1209
|
+
expect((await svc.eligibleReaders(workspaceId, 'Public/Notes.md')).restricted).toBe(false);
|
|
1149
1210
|
});
|
|
1150
1211
|
|
|
1151
1212
|
it('is matched case-insensitively, like every other email here', async () => {
|
|
@@ -185,13 +185,44 @@ describe('ChangeReadGate', () => {
|
|
|
185
185
|
|
|
186
186
|
it('the rescues name FILES: a folder target at the root is judged as a folder, even for an admin', async () => {
|
|
187
187
|
// The KB clone's own folder, asked about as an extraction destination:
|
|
188
|
-
// the root scope
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
188
|
+
// the root scope. It grants nobody here, so a non-admin is refused; an
|
|
189
|
+
// admin reads it through the root floor (write implies read), which is
|
|
190
|
+
// the access tree's own answer, not a rescue.
|
|
191
|
+
expect(await judge(ALICE, KB, 'dir')).toEqual({ allowed: false, unreadable: '' });
|
|
192
|
+
expect(await judge(ALICE, `${KB}/KnowledgeBase`, 'dir')).toEqual({ allowed: false, unreadable: 'KnowledgeBase' });
|
|
193
|
+
expect(await judge(ADMIN, KB, 'dir')).toEqual({ allowed: true, via: 'readable' });
|
|
194
|
+
expect(await judge(ADMIN, `${KB}/KnowledgeBase`, 'dir')).toEqual({ allowed: true, via: 'readable' });
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('an admin may add a loose file directly in the knowledge folder, with or without a root access.md', async () => {
|
|
198
|
+
// The reported case: an admin could make a folder under the knowledge
|
|
199
|
+
// root (the new-folder exception) and write inside it, but not a page
|
|
200
|
+
// directly in the root — the floor gave them write there and nothing
|
|
201
|
+
// gave them read. Write implies read, so the floor gives both.
|
|
202
|
+
expect(await judge(ADMIN, `${KB}/KnowledgeBase/notes.md`)).toEqual({ allowed: true, via: 'readable' });
|
|
192
203
|
await write(repo, 'access.md', `---\nowner:\n - Admin\n---\nwrite:\n - Admin\n`);
|
|
193
204
|
access.invalidate(WS);
|
|
194
|
-
expect(await judge(ADMIN, KB
|
|
205
|
+
expect(await judge(ADMIN, `${KB}/KnowledgeBase/notes.md`)).toEqual({ allowed: true, via: 'readable' });
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('the deployment owner is an admin for this gate too, whether or not roles.yaml names them', async () => {
|
|
209
|
+
const OWNER = 'owner@bevel.software';
|
|
210
|
+
const ws = stubWorkspaceService(path.join(root, WS));
|
|
211
|
+
const owned = new ChangeReadGate(
|
|
212
|
+
ws,
|
|
213
|
+
new AccessControlService(ws, KB, new NodeFs(), [OWNER]),
|
|
214
|
+
testKbContext({ kbDirName: KB }),
|
|
215
|
+
new NodeFs(),
|
|
216
|
+
);
|
|
217
|
+
await write(repo, 'KnowledgeBase/Board/access.md', '---\nread:\n - deny Admin\n---\n');
|
|
218
|
+
expect(await owned.judge(WS, OWNER, `${KB}/KnowledgeBase/notes.md`, 'file')).toEqual({ allowed: true, via: 'readable' });
|
|
219
|
+
// Excluded exactly where the Admin role is.
|
|
220
|
+
expect(await owned.judge(WS, OWNER, `${KB}/KnowledgeBase/Board/memo.md`, 'file')).toEqual({
|
|
221
|
+
allowed: false,
|
|
222
|
+
unreadable: 'KnowledgeBase/Board',
|
|
223
|
+
});
|
|
224
|
+
// Not an owner on the gate's own service: no floor, no read.
|
|
225
|
+
expect(await judge(OWNER, `${KB}/KnowledgeBase/notes.md`)).toEqual({ allowed: false, unreadable: 'KnowledgeBase' });
|
|
195
226
|
});
|
|
196
227
|
|
|
197
228
|
it('a roles.yaml that is there but unusable is not an open door', async () => {
|
|
@@ -149,8 +149,11 @@ export type AccessDecisionSource = {
|
|
|
149
149
|
* `via` names the tier that matched and `principal` the display name of the
|
|
150
150
|
* group, role or plugin principal it matched on (the caller's own email for
|
|
151
151
|
* `person`, `everyone` for the built-in). `admin-floor` is Admin's write at
|
|
152
|
-
* the repository root,
|
|
153
|
-
*
|
|
152
|
+
* the repository root, and the read that write implies, which no rule there
|
|
153
|
+
* can take away; its source is the scope the floor decided at — the root's
|
|
154
|
+
* rules (`folder`), or the own `frontmatter` of a file directly in the root,
|
|
155
|
+
* whose entries are part of the root and cannot take the floor away either —
|
|
156
|
+
* or null when the walk found no scope at all.
|
|
154
157
|
*/
|
|
155
158
|
export interface AccessDecision {
|
|
156
159
|
allowed: boolean;
|