@bevel-software/platform-core-backend 0.11.1 → 0.12.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/THIRD-PARTY-NOTICES.md +1163 -425
- package/dist/core/create-core-server.js +1 -1
- package/dist/core/create-core-server.js.map +1 -1
- package/dist/core/create-core-services.d.ts +3 -1
- package/dist/core/create-core-services.d.ts.map +1 -1
- package/dist/core/create-core-services.js +7 -2
- package/dist/core/create-core-services.js.map +1 -1
- package/dist/core-config.d.ts +7 -0
- package/dist/core-config.d.ts.map +1 -1
- package/dist/core-config.js +9 -0
- package/dist/core-config.js.map +1 -1
- package/dist/modules/access/access-control.service.d.ts +1 -253
- package/dist/modules/access/access-control.service.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.js +3 -632
- package/dist/modules/access/access-control.service.js.map +1 -1
- package/dist/modules/access/access-declarations.d.ts +2 -2
- package/dist/modules/access/access-declarations.d.ts.map +1 -1
- package/dist/modules/access/access-declarations.js +2 -2
- package/dist/modules/access/access-declarations.js.map +1 -1
- package/dist/modules/access/access-mutation.service.d.ts +3 -3
- package/dist/modules/access/access-mutation.service.d.ts.map +1 -1
- package/dist/modules/access/access-mutation.service.js +3 -3
- package/dist/modules/access/access-mutation.service.js.map +1 -1
- package/dist/modules/access/access.routes.js +4 -4
- package/dist/modules/access/access.routes.js.map +1 -1
- package/dist/modules/access/admin-locked-commit.d.ts +2 -2
- package/dist/modules/access/admin-locked-commit.d.ts.map +1 -1
- package/dist/modules/access/admin-locked-commit.js +2 -2
- package/dist/modules/access/admin-locked-commit.js.map +1 -1
- package/dist/modules/access/admin-route-helpers.js +1 -1
- package/dist/modules/access/admin-route-helpers.js.map +1 -1
- package/dist/modules/access/capability-registry.js +1 -1
- package/dist/modules/access/capability-registry.js.map +1 -1
- package/dist/modules/access/creator-access.d.ts +1 -45
- package/dist/modules/access/creator-access.d.ts.map +1 -1
- package/dist/modules/access/creator-access.js +4 -24
- package/dist/modules/access/creator-access.js.map +1 -1
- package/dist/modules/access/groups-admin.routes.js +1 -1
- package/dist/modules/access/groups-admin.routes.js.map +1 -1
- package/dist/modules/access/groups-admin.service.d.ts +1 -1
- package/dist/modules/access/groups-admin.service.d.ts.map +1 -1
- package/dist/modules/access/groups-admin.service.js +6 -5
- package/dist/modules/access/groups-admin.service.js.map +1 -1
- package/dist/modules/access/groups-edit.js +2 -2
- package/dist/modules/access/groups-edit.js.map +1 -1
- package/dist/modules/access/reference-scan.js +1 -1
- package/dist/modules/access/reference-scan.js.map +1 -1
- package/dist/modules/access/roles-admin.service.d.ts +1 -1
- package/dist/modules/access/roles-admin.service.d.ts.map +1 -1
- package/dist/modules/access/roles-admin.service.js +8 -7
- package/dist/modules/access/roles-admin.service.js.map +1 -1
- package/dist/modules/access/roles-edit.js +1 -1
- package/dist/modules/access/roles-edit.js.map +1 -1
- package/dist/modules/access/synced-groups-committer.js +4 -4
- package/dist/modules/access/synced-groups-committer.js.map +1 -1
- package/dist/modules/access/synced-groups-writer.js +2 -2
- package/dist/modules/access/synced-groups-writer.js.map +1 -1
- package/dist/modules/access-model/access-errors.d.ts +34 -0
- package/dist/modules/access-model/access-errors.d.ts.map +1 -0
- package/dist/modules/access-model/access-errors.js +40 -0
- package/dist/modules/access-model/access-errors.js.map +1 -0
- package/dist/modules/access-model/access-grammar.d.ts +266 -0
- package/dist/modules/access-model/access-grammar.d.ts.map +1 -0
- package/dist/modules/access-model/access-grammar.js +644 -0
- package/dist/modules/access-model/access-grammar.js.map +1 -0
- package/dist/modules/access-model/access-splice.d.ts +140 -0
- package/dist/modules/access-model/access-splice.d.ts.map +1 -0
- package/dist/modules/access-model/access-splice.js +389 -0
- package/dist/modules/access-model/access-splice.js.map +1 -0
- package/dist/modules/access-model/creator.d.ts +54 -0
- package/dist/modules/access-model/creator.d.ts.map +1 -0
- package/dist/modules/access-model/creator.js +30 -0
- package/dist/modules/access-model/creator.js.map +1 -0
- package/dist/modules/access-model/group-files.d.ts +83 -0
- package/dist/modules/access-model/group-files.d.ts.map +1 -0
- package/dist/modules/access-model/group-files.js +167 -0
- package/dist/modules/access-model/group-files.js.map +1 -0
- package/dist/modules/access-model/kb-read-filter.d.ts +41 -0
- package/dist/modules/access-model/kb-read-filter.d.ts.map +1 -0
- package/dist/modules/access-model/kb-read-filter.js +60 -0
- package/dist/modules/access-model/kb-read-filter.js.map +1 -0
- package/dist/modules/access-model/render-roles-yaml.d.ts +22 -0
- package/dist/modules/access-model/render-roles-yaml.d.ts.map +1 -0
- package/dist/modules/access-model/render-roles-yaml.js +56 -0
- package/dist/modules/access-model/render-roles-yaml.js.map +1 -0
- package/dist/modules/access-model/roles-yaml-guard.d.ts +56 -0
- package/dist/modules/access-model/roles-yaml-guard.d.ts.map +1 -0
- package/dist/modules/access-model/roles-yaml-guard.js +79 -0
- package/dist/modules/access-model/roles-yaml-guard.js.map +1 -0
- package/dist/modules/admin/admin-access.service.d.ts.map +1 -1
- package/dist/modules/admin/admin-access.service.js +1 -1
- package/dist/modules/admin/admin-access.service.js.map +1 -1
- package/dist/modules/code-mode/code-mode.tool.d.ts.map +1 -1
- package/dist/modules/code-mode/code-mode.tool.js +7 -1
- package/dist/modules/code-mode/code-mode.tool.js.map +1 -1
- package/dist/modules/diff/diff.routes.js +4 -4
- package/dist/modules/diff/diff.routes.js.map +1 -1
- package/dist/modules/diff/diff.service.d.ts +1 -1
- package/dist/modules/diff/diff.service.d.ts.map +1 -1
- package/dist/modules/kb-fs/branch-name.d.ts +10 -0
- package/dist/modules/kb-fs/branch-name.d.ts.map +1 -0
- package/dist/modules/kb-fs/branch-name.js +76 -0
- package/dist/modules/kb-fs/branch-name.js.map +1 -0
- package/dist/modules/kb-fs/clone-config.d.ts +61 -0
- package/dist/modules/kb-fs/clone-config.d.ts.map +1 -0
- package/dist/modules/kb-fs/clone-config.js +69 -0
- package/dist/modules/kb-fs/clone-config.js.map +1 -0
- package/dist/modules/kb-fs/file-change-notifier.d.ts +38 -0
- package/dist/modules/kb-fs/file-change-notifier.d.ts.map +1 -0
- package/dist/modules/kb-fs/file-change-notifier.js +22 -0
- package/dist/modules/kb-fs/file-change-notifier.js.map +1 -0
- package/dist/modules/kb-fs/locking-filesystem.d.ts +137 -0
- package/dist/modules/kb-fs/locking-filesystem.d.ts.map +1 -0
- package/dist/modules/kb-fs/locking-filesystem.js +553 -0
- package/dist/modules/kb-fs/locking-filesystem.js.map +1 -0
- package/dist/modules/kb-fs/mutex.d.ts +11 -0
- package/dist/modules/kb-fs/mutex.d.ts.map +1 -0
- package/dist/modules/kb-fs/mutex.js +23 -0
- package/dist/modules/kb-fs/mutex.js.map +1 -0
- package/dist/modules/kb-fs/read-only-filesystem.d.ts +24 -0
- package/dist/modules/kb-fs/read-only-filesystem.d.ts.map +1 -0
- package/dist/modules/kb-fs/read-only-filesystem.js +39 -0
- package/dist/modules/kb-fs/read-only-filesystem.js.map +1 -0
- package/dist/modules/plugins/join-proposals.d.ts +1 -1
- package/dist/modules/plugins/join-proposals.d.ts.map +1 -1
- package/dist/modules/plugins/join-proposals.js +1 -1
- package/dist/modules/plugins/join-proposals.js.map +1 -1
- package/dist/modules/plugins/join-requests.service.d.ts.map +1 -1
- package/dist/modules/plugins/join-requests.service.js +1 -1
- package/dist/modules/plugins/join-requests.service.js.map +1 -1
- package/dist/modules/plugins/plugin-provision.service.js +3 -3
- package/dist/modules/plugins/plugin-provision.service.js.map +1 -1
- package/dist/modules/plugins/plugins.routes.js +2 -2
- package/dist/modules/plugins/plugins.routes.js.map +1 -1
- package/dist/modules/plugins/plugins.service.js +1 -1
- package/dist/modules/plugins/plugins.service.js.map +1 -1
- package/dist/modules/secrets-vault/secrets-vault.routes.js +1 -1
- package/dist/modules/secrets-vault/secrets-vault.routes.js.map +1 -1
- package/dist/modules/skills/pending-skills.service.d.ts.map +1 -1
- package/dist/modules/skills/pending-skills.service.js +1 -1
- package/dist/modules/skills/pending-skills.service.js.map +1 -1
- package/dist/modules/skills/skills.service.js +1 -1
- package/dist/modules/skills/skills.service.js.map +1 -1
- package/dist/modules/tool-helpers/tool-context.d.ts +2 -2
- package/dist/modules/tool-helpers/tool-context.d.ts.map +1 -1
- package/dist/modules/tool-helpers/tool-context.js +4 -4
- package/dist/modules/tool-helpers/tool-context.js.map +1 -1
- package/dist/modules/tool-manuals/mcp-server-edit.service.d.ts +1 -1
- package/dist/modules/tool-manuals/mcp-server-edit.service.d.ts.map +1 -1
- package/dist/modules/tool-manuals/mcp-server-edit.service.js +1 -1
- package/dist/modules/tool-manuals/mcp-server-edit.service.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.routes.d.ts +1 -1
- package/dist/modules/tool-manuals/tool-manuals.routes.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.routes.js +1 -1
- package/dist/modules/tool-manuals/tool-manuals.routes.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.service.js +1 -1
- package/dist/modules/tool-manuals/tool-manuals.service.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.tools.js +1 -1
- package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
- package/dist/modules/workflow/agent-tools/workflow.tools.js +1 -1
- package/dist/modules/workflow/agent-tools/workflow.tools.js.map +1 -1
- package/dist/modules/workflow/file-lock.service.js +1 -1
- package/dist/modules/workflow/file-lock.service.js.map +1 -1
- package/dist/modules/workflow/git/git.service.d.ts +2 -2
- package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
- package/dist/modules/workflow/git/git.service.js +5 -5
- package/dist/modules/workflow/git/git.service.js.map +1 -1
- package/dist/modules/workflow/git/pull-request.service.js +1 -1
- package/dist/modules/workflow/git/pull-request.service.js.map +1 -1
- package/dist/modules/workflow/review-workflow/review-workflow.service.js +2 -2
- package/dist/modules/workflow/review-workflow/review-workflow.service.js.map +1 -1
- package/dist/modules/workflow/session-ontology.service.js +1 -1
- package/dist/modules/workflow/session-ontology.service.js.map +1 -1
- package/dist/modules/workflow/workflow.routes.js +2 -2
- package/dist/modules/workflow/workflow.routes.js.map +1 -1
- package/dist/modules/workflow/workflow.service.d.ts +1 -1
- package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
- package/dist/modules/workflow/workflow.service.js +4 -4
- package/dist/modules/workflow/workflow.service.js.map +1 -1
- package/dist/modules/workspace/file-readers/doc-extract.service.d.ts +71 -0
- package/dist/modules/workspace/file-readers/doc-extract.service.d.ts.map +1 -0
- package/dist/modules/workspace/file-readers/doc-extract.service.js +90 -0
- package/dist/modules/workspace/file-readers/doc-extract.service.js.map +1 -0
- package/dist/modules/workspace/file-readers/doc-extract.types.d.ts +55 -0
- package/dist/modules/workspace/file-readers/doc-extract.types.d.ts.map +1 -0
- package/dist/modules/workspace/file-readers/doc-extract.types.js +34 -0
- package/dist/modules/workspace/file-readers/doc-extract.types.js.map +1 -0
- package/dist/modules/workspace/file-readers/document-reader.d.ts +32 -0
- package/dist/modules/workspace/file-readers/document-reader.d.ts.map +1 -0
- package/dist/modules/workspace/file-readers/document-reader.js +59 -0
- package/dist/modules/workspace/file-readers/document-reader.js.map +1 -0
- package/dist/modules/workspace/file-readers/email-reader.d.ts +15 -0
- package/dist/modules/workspace/file-readers/email-reader.d.ts.map +1 -0
- package/dist/modules/workspace/file-readers/email-reader.js +19 -0
- package/dist/modules/workspace/file-readers/email-reader.js.map +1 -0
- package/dist/modules/workspace/file-readers/email-text.d.ts +51 -0
- package/dist/modules/workspace/file-readers/email-text.d.ts.map +1 -0
- package/dist/modules/workspace/file-readers/email-text.js +151 -0
- package/dist/modules/workspace/file-readers/email-text.js.map +1 -0
- package/dist/modules/workspace/file-readers/extract-docx.d.ts +13 -0
- package/dist/modules/workspace/file-readers/extract-docx.d.ts.map +1 -0
- package/dist/modules/workspace/file-readers/extract-docx.js +67 -0
- package/dist/modules/workspace/file-readers/extract-docx.js.map +1 -0
- package/dist/modules/workspace/file-readers/extract-eml.d.ts +18 -0
- package/dist/modules/workspace/file-readers/extract-eml.d.ts.map +1 -0
- package/dist/modules/workspace/file-readers/extract-eml.js +87 -0
- package/dist/modules/workspace/file-readers/extract-eml.js.map +1 -0
- package/dist/modules/workspace/file-readers/extract-msg.d.ts +17 -0
- package/dist/modules/workspace/file-readers/extract-msg.d.ts.map +1 -0
- package/dist/modules/workspace/file-readers/extract-msg.js +121 -0
- package/dist/modules/workspace/file-readers/extract-msg.js.map +1 -0
- package/dist/modules/workspace/file-readers/extract-odp.d.ts +13 -0
- package/dist/modules/workspace/file-readers/extract-odp.d.ts.map +1 -0
- package/dist/modules/workspace/file-readers/extract-odp.js +60 -0
- package/dist/modules/workspace/file-readers/extract-odp.js.map +1 -0
- package/dist/modules/workspace/file-readers/extract-ods.d.ts +10 -0
- package/dist/modules/workspace/file-readers/extract-ods.d.ts.map +1 -0
- package/dist/modules/workspace/file-readers/extract-ods.js +173 -0
- package/dist/modules/workspace/file-readers/extract-ods.js.map +1 -0
- package/dist/modules/workspace/file-readers/extract-odt.d.ts +17 -0
- package/dist/modules/workspace/file-readers/extract-odt.d.ts.map +1 -0
- package/dist/modules/workspace/file-readers/extract-odt.js +45 -0
- package/dist/modules/workspace/file-readers/extract-odt.js.map +1 -0
- package/dist/modules/workspace/file-readers/extract-pdf.d.ts +3 -0
- package/dist/modules/workspace/file-readers/extract-pdf.d.ts.map +1 -0
- package/dist/modules/workspace/file-readers/extract-pdf.js +176 -0
- package/dist/modules/workspace/file-readers/extract-pdf.js.map +1 -0
- package/dist/modules/workspace/file-readers/extract-pptx.d.ts +37 -0
- package/dist/modules/workspace/file-readers/extract-pptx.d.ts.map +1 -0
- package/dist/modules/workspace/file-readers/extract-pptx.js +288 -0
- package/dist/modules/workspace/file-readers/extract-pptx.js.map +1 -0
- package/dist/modules/workspace/file-readers/extract-xlsx.d.ts +10 -0
- package/dist/modules/workspace/file-readers/extract-xlsx.d.ts.map +1 -0
- package/dist/modules/workspace/file-readers/extract-xlsx.js +98 -0
- package/dist/modules/workspace/file-readers/extract-xlsx.js.map +1 -0
- package/dist/modules/workspace/file-readers/extraction-cache.d.ts +61 -0
- package/dist/modules/workspace/file-readers/extraction-cache.d.ts.map +1 -0
- package/dist/modules/workspace/file-readers/extraction-cache.js +135 -0
- package/dist/modules/workspace/file-readers/extraction-cache.js.map +1 -0
- package/dist/modules/workspace/file-readers/file-reader.d.ts +76 -0
- package/dist/modules/workspace/file-readers/file-reader.d.ts.map +1 -0
- package/dist/modules/workspace/file-readers/file-reader.js +55 -0
- package/dist/modules/workspace/file-readers/file-reader.js.map +1 -0
- package/dist/modules/workspace/file-readers/file-reader.registry.d.ts +13 -0
- package/dist/modules/workspace/file-readers/file-reader.registry.d.ts.map +1 -0
- package/dist/modules/workspace/file-readers/file-reader.registry.js +41 -0
- package/dist/modules/workspace/file-readers/file-reader.registry.js.map +1 -0
- package/dist/modules/workspace/file-readers/image-read.d.ts +35 -0
- package/dist/modules/workspace/file-readers/image-read.d.ts.map +1 -0
- package/dist/modules/workspace/file-readers/image-read.js +108 -0
- package/dist/modules/workspace/file-readers/image-read.js.map +1 -0
- package/dist/modules/workspace/file-readers/image-reader.d.ts +19 -0
- package/dist/modules/workspace/file-readers/image-reader.d.ts.map +1 -0
- package/dist/modules/workspace/file-readers/image-reader.js +30 -0
- package/dist/modules/workspace/file-readers/image-reader.js.map +1 -0
- package/dist/modules/workspace/file-readers/odf-text.d.ts +26 -0
- package/dist/modules/workspace/file-readers/odf-text.d.ts.map +1 -0
- package/dist/modules/workspace/file-readers/odf-text.js +116 -0
- package/dist/modules/workspace/file-readers/odf-text.js.map +1 -0
- package/dist/modules/workspace/file-readers/ooxml-text.d.ts +172 -0
- package/dist/modules/workspace/file-readers/ooxml-text.d.ts.map +1 -0
- package/dist/modules/workspace/file-readers/ooxml-text.js +439 -0
- package/dist/modules/workspace/file-readers/ooxml-text.js.map +1 -0
- package/dist/modules/workspace/file-readers/text-reader.d.ts +47 -0
- package/dist/modules/workspace/file-readers/text-reader.d.ts.map +1 -0
- package/dist/modules/workspace/file-readers/text-reader.js +117 -0
- package/dist/modules/workspace/file-readers/text-reader.js.map +1 -0
- package/dist/modules/workspace/startup/kb-startup-runner.js +1 -1
- package/dist/modules/workspace/startup/kb-startup-runner.js.map +1 -1
- package/dist/modules/workspace/startup/steps/groups-to-plugins.step.js +1 -1
- package/dist/modules/workspace/startup/steps/groups-to-plugins.step.js.map +1 -1
- package/dist/modules/workspace/startup/steps/roles-yaml.step.d.ts +1 -1
- package/dist/modules/workspace/startup/steps/roles-yaml.step.js +2 -2
- package/dist/modules/workspace/startup/steps/roles-yaml.step.js.map +1 -1
- package/dist/modules/workspace/startup/steps/seed-tree.js +1 -1
- package/dist/modules/workspace/startup/steps/seed-tree.js.map +1 -1
- package/dist/modules/workspace/workspace.routes.d.ts +1 -1
- package/dist/modules/workspace/workspace.routes.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.routes.js +5 -4
- package/dist/modules/workspace/workspace.routes.js.map +1 -1
- package/dist/modules/workspace/workspace.service.d.ts +2 -9
- package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.service.js +14 -15
- package/dist/modules/workspace/workspace.service.js.map +1 -1
- package/dist/modules/workspace/workspace.tools.d.ts +2 -1
- package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.tools.js +161 -18
- package/dist/modules/workspace/workspace.tools.js.map +1 -1
- package/dist/shared/domain-errors.d.ts +202 -0
- package/dist/shared/domain-errors.d.ts.map +1 -0
- package/dist/shared/domain-errors.js +303 -0
- package/dist/shared/domain-errors.js.map +1 -0
- package/dist/shared/workspace-id.d.ts +27 -0
- package/dist/shared/workspace-id.d.ts.map +1 -0
- package/dist/shared/workspace-id.js +36 -0
- package/dist/shared/workspace-id.js.map +1 -0
- package/package.json +9 -4
- package/src/core/create-core-server.ts +1 -1
- package/src/core/create-core-services.ts +8 -2
- package/src/core-config.ts +9 -0
- package/src/modules/access/__tests__/access-control.service.test.ts +1 -1
- package/src/modules/access/__tests__/access-groups.test.ts +6 -5
- package/src/modules/access/__tests__/access-md-format.test.ts +3 -3
- package/src/modules/access/__tests__/access-mutation.service.test.ts +1 -1
- package/src/modules/access/__tests__/admin-locked-commit.test.ts +1 -1
- package/src/modules/access/__tests__/admin-route-helpers.test.ts +1 -1
- package/src/modules/access/__tests__/roles-admin.service.test.ts +2 -2
- package/src/modules/access/__tests__/roles-edit.test.ts +1 -1
- package/src/modules/access/__tests__/synced-groups-committer.test.ts +1 -1
- package/src/modules/access/__tests__/synced-groups-writer.test.ts +1 -1
- package/src/modules/access/access-control.service.ts +23 -768
- package/src/modules/access/access-declarations.ts +2 -2
- package/src/modules/access/access-mutation.service.ts +3 -3
- package/src/modules/access/access.routes.ts +5 -5
- package/src/modules/access/admin-locked-commit.ts +2 -2
- package/src/modules/access/admin-route-helpers.ts +1 -1
- package/src/modules/access/capability-registry.ts +1 -1
- package/src/modules/access/creator-access.ts +9 -72
- package/src/modules/access/groups-admin.routes.ts +1 -1
- package/src/modules/access/groups-admin.service.ts +6 -7
- package/src/modules/access/groups-edit.ts +2 -2
- package/src/modules/access/reference-scan.ts +1 -1
- package/src/modules/access/roles-admin.service.ts +8 -8
- package/src/modules/access/roles-edit.ts +1 -1
- package/src/modules/access/synced-groups-committer.ts +4 -4
- package/src/modules/access/synced-groups-writer.ts +2 -2
- package/src/modules/access-model/__tests__/access-grammar.test.ts +31 -0
- package/src/modules/{access → access-model}/__tests__/access-splice.test.ts +268 -268
- package/src/modules/{access → access-model}/access-errors.ts +1 -1
- package/src/modules/access-model/access-grammar.ts +778 -0
- package/src/modules/{access → access-model}/access-splice.ts +1 -1
- package/src/modules/access-model/creator.ts +79 -0
- package/src/modules/{access → access-model}/group-files.ts +1 -1
- package/src/modules/{access → access-model}/render-roles-yaml.ts +1 -1
- package/src/modules/{access → access-model}/roles-yaml-guard.ts +2 -2
- package/src/modules/admin/admin-access.service.ts +2 -1
- package/src/modules/code-mode/__tests__/code-mode.tool.test.ts +30 -0
- package/src/modules/code-mode/code-mode.tool.ts +7 -1
- package/src/modules/diff/__tests__/diff.routes.rejectPathsLocked.test.ts +2 -2
- package/src/modules/diff/__tests__/diff.service.seed-atomicity.test.ts +3 -2
- package/src/modules/diff/__tests__/diff.service.test.ts +3 -2
- package/src/modules/diff/diff.routes.ts +4 -4
- package/src/modules/diff/diff.service.ts +1 -1
- package/src/modules/{workflow/git → kb-fs}/__tests__/branch-name.test.ts +1 -1
- package/src/modules/{workflow → kb-fs}/__tests__/locking-filesystem.test.ts +1 -1
- package/src/modules/{workflow/git → kb-fs}/branch-name.ts +1 -1
- package/src/modules/{workflow → kb-fs}/locking-filesystem.ts +2 -2
- package/src/modules/plugins/__tests__/join-requests.service.test.ts +2 -4
- package/src/modules/plugins/__tests__/plugin-index.service.test.ts +1 -1
- package/src/modules/plugins/__tests__/plugins.routes.test.ts +1 -1
- package/src/modules/plugins/join-proposals.ts +1 -1
- package/src/modules/plugins/join-requests.service.ts +2 -1
- package/src/modules/plugins/plugin-provision.service.ts +3 -3
- package/src/modules/plugins/plugins.routes.ts +2 -2
- package/src/modules/plugins/plugins.service.ts +1 -1
- package/src/modules/secrets-vault/secrets-vault.routes.ts +1 -1
- package/src/modules/skills/__tests__/skills.service.test.ts +1 -1
- package/src/modules/skills/pending-skills.service.ts +2 -1
- package/src/modules/skills/skills.service.ts +1 -1
- package/src/modules/tool-helpers/__tests__/phase4-tools.test.ts +2 -1
- package/src/modules/tool-helpers/tool-context.ts +6 -5
- package/src/modules/tool-manuals/__tests__/mcp-server-edit.service.test.ts +1 -1
- package/src/modules/tool-manuals/__tests__/tool-manuals.archive.route.test.ts +1 -1
- package/src/modules/tool-manuals/__tests__/tool-manuals.detail.route.test.ts +1 -1
- package/src/modules/tool-manuals/__tests__/tool-manuals.mcp-oauth.test.ts +1 -1
- package/src/modules/tool-manuals/__tests__/tool-manuals.service.test.ts +1 -1
- package/src/modules/tool-manuals/mcp-server-edit.service.ts +2 -1
- package/src/modules/tool-manuals/tool-manuals.routes.ts +2 -1
- package/src/modules/tool-manuals/tool-manuals.service.ts +1 -1
- package/src/modules/tool-manuals/tool-manuals.tools.ts +1 -1
- package/src/modules/workflow/__tests__/preserve-roles-yaml.test.ts +1 -1
- package/src/modules/workflow/__tests__/workflow.service.commitFileWhileLocked.test.ts +1 -1
- package/src/modules/workflow/__tests__/workflow.service.facade.test.ts +1 -1
- package/src/modules/workflow/__tests__/workflow.service.releaseLock.test.ts +1 -1
- package/src/modules/workflow/agent-tools/workflow.tools.ts +1 -1
- package/src/modules/workflow/file-lock.service.ts +1 -1
- package/src/modules/workflow/git/__tests__/git.service.accessGating.test.ts +1 -1
- package/src/modules/workflow/git/__tests__/git.service.deleteBranch.test.ts +1 -1
- package/src/modules/workflow/git/__tests__/git.service.diffFileAtCommit.test.ts +1 -1
- package/src/modules/workflow/git/__tests__/git.service.diffFileBetweenBranches.test.ts +1 -1
- package/src/modules/workflow/git/__tests__/git.service.pull.test.ts +1 -1
- package/src/modules/workflow/git/git.service.ts +5 -5
- package/src/modules/workflow/git/pull-request.service.ts +1 -1
- package/src/modules/workflow/review-workflow/__tests__/cancel-pr.test.ts +1 -1
- package/src/modules/workflow/review-workflow/review-workflow.service.ts +2 -2
- package/src/modules/workflow/session-ontology.service.ts +1 -1
- package/src/modules/workflow/workflow.routes.ts +2 -2
- package/src/modules/workflow/workflow.service.ts +5 -5
- package/src/modules/workspace/__tests__/workspace.routes.create-grant.test.ts +1 -1
- package/src/modules/workspace/__tests__/workspace.routes.delete.test.ts +1 -1
- package/src/modules/workspace/__tests__/workspace.routes.download.test.ts +2 -2
- package/src/modules/workspace/__tests__/workspace.routes.read-gate.test.ts +1 -1
- package/src/modules/workspace/__tests__/workspace.service.read-filter.test.ts +2 -5
- package/src/modules/workspace/__tests__/workspace.service.test.ts +13 -1
- package/src/modules/workspace/__tests__/workspace.tools.test.ts +501 -3
- package/src/modules/workspace/file-readers/__tests__/doc-extract.test.ts +1658 -0
- package/src/modules/workspace/file-readers/__tests__/email-extract.test.ts +485 -0
- package/src/modules/workspace/file-readers/__tests__/file-reader.registry.test.ts +97 -0
- package/src/modules/workspace/file-readers/__tests__/image-read.test.ts +100 -0
- package/src/modules/workspace/file-readers/doc-extract.service.ts +104 -0
- package/src/modules/workspace/file-readers/doc-extract.types.ts +63 -0
- package/src/modules/workspace/file-readers/document-reader.ts +64 -0
- package/src/modules/workspace/file-readers/email-reader.ts +21 -0
- package/src/modules/workspace/file-readers/email-text.ts +193 -0
- package/src/modules/workspace/file-readers/extract-docx.ts +67 -0
- package/src/modules/workspace/file-readers/extract-eml.ts +92 -0
- package/src/modules/workspace/file-readers/extract-msg.ts +134 -0
- package/src/modules/workspace/file-readers/extract-odp.ts +63 -0
- package/src/modules/workspace/file-readers/extract-ods.ts +182 -0
- package/src/modules/workspace/file-readers/extract-odt.ts +48 -0
- package/src/modules/workspace/file-readers/extract-pdf.ts +178 -0
- package/src/modules/workspace/file-readers/extract-pptx.ts +302 -0
- package/src/modules/workspace/file-readers/extract-xlsx.ts +96 -0
- package/src/modules/workspace/file-readers/extraction-cache.ts +142 -0
- package/src/modules/workspace/file-readers/file-reader.registry.ts +45 -0
- package/src/modules/workspace/file-readers/file-reader.ts +104 -0
- package/src/modules/workspace/file-readers/image-read.ts +122 -0
- package/src/modules/workspace/file-readers/image-reader.ts +39 -0
- package/src/modules/workspace/file-readers/odf-text.ts +123 -0
- package/src/modules/workspace/file-readers/ooxml-text.ts +477 -0
- package/src/modules/workspace/file-readers/text-reader.ts +131 -0
- package/src/modules/workspace/startup/kb-startup-runner.ts +1 -1
- package/src/modules/workspace/startup/steps/__tests__/steps.test.ts +1 -1
- package/src/modules/workspace/startup/steps/groups-to-plugins.step.ts +1 -1
- package/src/modules/workspace/startup/steps/roles-yaml.step.ts +2 -2
- package/src/modules/workspace/startup/steps/seed-tree.ts +1 -1
- package/src/modules/workspace/workspace.routes.ts +6 -5
- package/src/modules/workspace/workspace.service.ts +14 -18
- package/src/modules/workspace/workspace.tools.ts +177 -15
- package/src/shared/__tests__/join-request.test.ts +1 -1
- package/src/shared/__tests__/workspace-id.test.ts +17 -0
- package/src/{modules/workflow/workflow.errors.ts → shared/domain-errors.ts} +6 -1
- package/src/shared/workspace-id.ts +36 -0
- /package/src/modules/{access → access-model}/__tests__/kb-read-filter.test.ts +0 -0
- /package/src/modules/{access → access-model}/__tests__/roles-yaml-guard.test.ts +0 -0
- /package/src/modules/{access → access-model}/kb-read-filter.ts +0 -0
- /package/src/modules/{workflow/git → kb-fs}/__tests__/clone-config.test.ts +0 -0
- /package/src/modules/{workflow → kb-fs}/__tests__/file-change-notifier.test.ts +0 -0
- /package/src/modules/{workflow/git → kb-fs}/__tests__/mutex.test.ts +0 -0
- /package/src/modules/{workflow/git → kb-fs}/clone-config.ts +0 -0
- /package/src/modules/{workflow → kb-fs}/file-change-notifier.ts +0 -0
- /package/src/modules/{workflow/git → kb-fs}/mutex.ts +0 -0
- /package/src/modules/{workflow → kb-fs}/read-only-filesystem.ts +0 -0
|
@@ -1,271 +1,271 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
spliceGrant,
|
|
5
|
-
spliceRevoke,
|
|
6
|
-
validatePrincipal,
|
|
7
|
-
AccessSpliceError,
|
|
8
|
-
type Principal,
|
|
9
|
-
} from '../access-splice.js';
|
|
10
|
-
import { parseAccessEntry, canonicalEmail } from '../access-
|
|
11
|
-
|
|
12
|
-
const felix: Principal = { kind: 'user', email: 'felix.kissel@example.com', displayName: 'Felix' };
|
|
13
|
-
const juan: Principal = { kind: 'user', email: 'juan@bevel.software', displayName: 'Juan V' };
|
|
14
|
-
const productTeam: Principal = { kind: 'role', role: 'Product Team' };
|
|
15
|
-
|
|
16
|
-
/** A realistic access.md with a prose body + comment — the thing parse→emit destroys. */
|
|
17
|
-
const ROOT_ACCESS_MD = `---
|
|
18
|
-
write:
|
|
19
|
-
- Admin
|
|
20
|
-
# download is independent of write
|
|
21
|
-
download:
|
|
22
|
-
- Juan <juan@bevel.software>
|
|
23
|
-
owner: []
|
|
24
|
-
---
|
|
25
|
-
|
|
26
|
-
# Repository access
|
|
27
|
-
|
|
28
|
-
This file is the root of the access-control tree. It grants write access only
|
|
29
|
-
to the \`Admin\` role by default. Subfolders can broaden access.
|
|
30
|
-
|
|
31
|
-
See \`lib/access-control.js\` for the resolution semantics.
|
|
32
|
-
`;
|
|
33
|
-
|
|
34
|
-
describe('spliceGrant — body + comment preservation (keystone)', () => {
|
|
35
|
-
it('keeps the markdown body, comments, and key order byte-for-byte', () => {
|
|
36
|
-
const r = spliceGrant(ROOT_ACCESS_MD, 'write', felix);
|
|
37
|
-
expect(r.changed).toBe(true);
|
|
38
|
-
// The body prose must survive verbatim.
|
|
39
|
-
expect(r.text).toContain('# Repository access');
|
|
40
|
-
expect(r.text).toContain('See `lib/access-control.js` for the resolution semantics.');
|
|
41
|
-
// The comment must survive.
|
|
42
|
-
expect(r.text).toContain('# download is independent of write');
|
|
43
|
-
// Existing entries untouched, new one appended under write.
|
|
44
|
-
expect(r.text).toContain(' - Admin');
|
|
45
|
-
expect(r.text).toContain(' - Felix <felix.kissel@example.com>');
|
|
46
|
-
// owner stays empty-list.
|
|
47
|
-
expect(r.text).toContain('owner: []');
|
|
48
|
-
// Felix lands under write, immediately after Admin, before the comment.
|
|
49
|
-
const lines = r.text.split('\n');
|
|
50
|
-
const adminIdx = lines.indexOf(' - Admin');
|
|
51
|
-
const felixIdx = lines.indexOf(' - Felix <felix.kissel@example.com>');
|
|
52
|
-
const commentIdx = lines.indexOf('# download is independent of write');
|
|
53
|
-
expect(adminIdx).toBeGreaterThan(-1);
|
|
54
|
-
expect(felixIdx).toBe(adminIdx + 1);
|
|
55
|
-
expect(commentIdx).toBeGreaterThan(felixIdx);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it('grant then revoke returns to a body-preserving state', () => {
|
|
59
|
-
const granted = spliceGrant(ROOT_ACCESS_MD, 'write', felix).text;
|
|
60
|
-
const revoked = spliceRevoke(granted, 'write', felix).text;
|
|
61
|
-
expect(revoked).toContain('# Repository access');
|
|
62
|
-
expect(revoked).toContain(' - Admin');
|
|
63
|
-
expect(revoked).not.toContain('Felix');
|
|
64
|
-
});
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
describe('spliceGrant — idempotency + creation', () => {
|
|
68
|
-
it('is a no-op when the principal already has the grant', () => {
|
|
69
|
-
const once = spliceGrant(ROOT_ACCESS_MD, 'write', felix).text;
|
|
70
|
-
const twice = spliceGrant(once, 'write', felix);
|
|
71
|
-
expect(twice.changed).toBe(false);
|
|
72
|
-
expect(twice.text).toBe(once);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it('creates the verb key when absent', () => {
|
|
76
|
-
const r = spliceGrant(ROOT_ACCESS_MD, 'read', productTeam);
|
|
77
|
-
expect(r.changed).toBe(true);
|
|
78
|
-
expect(r.text).toContain('read:');
|
|
79
|
-
expect(r.text).toContain(' - Product Team');
|
|
80
|
-
expect(r.text).toContain('# Repository access'); // body still there
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it('promotes an inline-empty `owner: []` to a block list', () => {
|
|
84
|
-
const r = spliceGrant(ROOT_ACCESS_MD, 'owner', juan);
|
|
85
|
-
expect(r.changed).toBe(true);
|
|
86
|
-
expect(r.text).toContain('owner:\n - Juan V <juan@bevel.software>');
|
|
87
|
-
expect(r.text).not.toContain('owner: []');
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
it('synthesises frontmatter for a fresh (frontmatter-less) file', () => {
|
|
91
|
-
const r = spliceGrant('', 'write', felix);
|
|
92
|
-
expect(r.changed).toBe(true);
|
|
93
|
-
expect(r.text.startsWith('---\n')).toBe(true);
|
|
94
|
-
expect(r.text).toContain('write:\n - Felix <felix.kissel@example.com>');
|
|
95
|
-
});
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
describe('spliceRevoke', () => {
|
|
99
|
-
it('removes one entry and collapses the verb to [] when it empties', () => {
|
|
100
|
-
const r = spliceRevoke(ROOT_ACCESS_MD, 'write', { kind: 'role', role: 'Admin' });
|
|
101
|
-
expect(r.changed).toBe(true);
|
|
102
|
-
expect(r.text).toContain('write: []');
|
|
103
|
-
expect(r.text).not.toMatch(/- Admin/);
|
|
104
|
-
expect(r.text).toContain('# Repository access');
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
it('is a no-op when the principal is not present', () => {
|
|
108
|
-
const r = spliceRevoke(ROOT_ACCESS_MD, 'write', felix);
|
|
109
|
-
expect(r.changed).toBe(false);
|
|
110
|
-
expect(r.text).toBe(ROOT_ACCESS_MD);
|
|
111
|
-
});
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
describe('spliceGrant — verbs are independent (a principal may hold several)', () => {
|
|
115
|
-
it('grants a second verb without removing the principal from the first', () => {
|
|
116
|
-
// Verbs do not nest: granting `download` on top of `write` leaves BOTH.
|
|
117
|
-
const granted = spliceGrant(ROOT_ACCESS_MD, 'write', felix).text;
|
|
118
|
-
const both = spliceGrant(granted, 'download', felix).text;
|
|
119
|
-
const writeBlock = both.slice(both.indexOf('write:'), both.indexOf('download:'));
|
|
120
|
-
expect(writeBlock).toContain('Felix'); // still under write
|
|
121
|
-
expect(both).toContain('download:'); // and now under download too
|
|
122
|
-
const downloadBlock = both.slice(both.indexOf('download:'));
|
|
123
|
-
expect(downloadBlock).toContain('Felix');
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
it('is idempotent for an already-granted verb', () => {
|
|
127
|
-
const granted = spliceGrant(ROOT_ACCESS_MD, 'owner', felix).text;
|
|
128
|
-
const again = spliceGrant(granted, 'owner', felix);
|
|
129
|
-
expect(again.changed).toBe(false);
|
|
130
|
-
});
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
describe('spliceGrant({ deny }) — the deny-here per-item override', () => {
|
|
134
|
-
it('adds a `deny <principal>` line to an existing block list', () => {
|
|
135
|
-
// `write:` already lists Admin; deny Felix there.
|
|
136
|
-
const r = spliceGrant(ROOT_ACCESS_MD, 'write', felix, { deny: true });
|
|
137
|
-
expect(r.changed).toBe(true);
|
|
138
|
-
const writeBlock = r.text.slice(r.text.indexOf('write:'), r.text.indexOf('download:'));
|
|
139
|
-
expect(writeBlock).toContain('- deny Felix <felix.kissel@example.com>');
|
|
140
|
-
expect(writeBlock).toContain('- Admin'); // existing grant untouched
|
|
141
|
-
// The denial parses back as a deny entry.
|
|
142
|
-
const entry = parseAccessEntry('deny Felix <felix.kissel@example.com>');
|
|
143
|
-
expect(entry.ok && entry.entry.deny).toBe(true);
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
it('promotes a node-frontmatter scalar to a block list with the deny entry', () => {
|
|
147
|
-
// A node whose own frontmatter names a sole owner via the scalar form.
|
|
148
|
-
const node = `---\nnodeType: process\nowner: Juan V <juan@bevel.software>\n---\n# Body\n`;
|
|
149
|
-
const r = spliceGrant(node, 'owner', felix, { deny: true, allowScalar: true });
|
|
150
|
-
expect(r.changed).toBe(true);
|
|
151
|
-
expect(r.text).toContain('owner:');
|
|
152
|
-
expect(r.text).toContain('- Juan V <juan@bevel.software>'); // old scalar preserved
|
|
153
|
-
expect(r.text).toContain('- deny Felix <felix.kissel@example.com>'); // deny added
|
|
154
|
-
expect(r.text).toContain('# Body'); // body survives
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
it('fills an inline-empty `owner: []` with the deny entry', () => {
|
|
158
|
-
const r = spliceGrant(ROOT_ACCESS_MD, 'owner', felix, { deny: true });
|
|
159
|
-
expect(r.changed).toBe(true);
|
|
160
|
-
const ownerBlock = r.text.slice(r.text.indexOf('owner:'));
|
|
161
|
-
expect(ownerBlock).toContain('- deny Felix <felix.kissel@example.com>');
|
|
162
|
-
expect(ownerBlock).not.toContain('owner: []'); // the [] collapsed to a real list
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
it('is a no-op when the same deny entry already exists', () => {
|
|
166
|
-
const once = spliceGrant(ROOT_ACCESS_MD, 'write', felix, { deny: true }).text;
|
|
167
|
-
const twice = spliceGrant(once, 'write', felix, { deny: true });
|
|
168
|
-
expect(twice.changed).toBe(false);
|
|
169
|
-
expect(twice.text).toBe(once);
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
it('a deny and a grant for the same principal are independent (deny does not no-op a grant)', () => {
|
|
173
|
-
// An existing grant must NOT make the deny idempotent, and vice-versa —
|
|
174
|
-
// the idempotency key includes deny-ness. (The resolver-level grant-beats-
|
|
175
|
-
// deny interaction is handled one layer up in AccessMutationService.)
|
|
176
|
-
const granted = spliceGrant(ROOT_ACCESS_MD, 'write', felix).text;
|
|
177
|
-
const denied = spliceGrant(granted, 'write', felix, { deny: true });
|
|
178
|
-
expect(denied.changed).toBe(true);
|
|
179
|
-
const writeBlock = denied.text.slice(denied.text.indexOf('write:'), denied.text.indexOf('download:'));
|
|
180
|
-
expect(writeBlock).toContain('- Felix <felix.kissel@example.com>');
|
|
181
|
-
expect(writeBlock).toContain('- deny Felix <felix.kissel@example.com>');
|
|
182
|
-
});
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
describe('validatePrincipal — injection guard', () => {
|
|
186
|
-
it('rejects a newline-bearing email (frontmatter injection)', () => {
|
|
187
|
-
expect(() =>
|
|
188
|
-
validatePrincipal({
|
|
189
|
-
kind: 'user',
|
|
190
|
-
email: 'x@y.com\nwrite:\n - everyone',
|
|
191
|
-
displayName: 'X',
|
|
192
|
-
}),
|
|
193
|
-
).toThrow(AccessSpliceError);
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
it('rejects a display name containing angle brackets', () => {
|
|
197
|
-
expect(() =>
|
|
198
|
-
validatePrincipal({ kind: 'user', email: 'a@b.com', displayName: 'Bad <hack>' }),
|
|
199
|
-
).toThrow(AccessSpliceError);
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
it.each([['\n'], ['\r'], ['\r\n'], [''], ['']])(
|
|
203
|
-
'rejects a display name carrying the control char %j (frontmatter injection)',
|
|
204
|
-
(ch) => {
|
|
205
|
-
expect(() =>
|
|
206
|
-
validatePrincipal({
|
|
207
|
-
kind: 'user',
|
|
208
|
-
email: 'a@b.com',
|
|
209
|
-
// Interior, so `trim()` cannot quietly rescue it on the way through.
|
|
210
|
-
displayName: `Ali${ch}read:${ch} - everyone`,
|
|
211
|
-
}),
|
|
212
|
-
).toThrow(AccessSpliceError);
|
|
213
|
-
},
|
|
214
|
-
);
|
|
215
|
-
|
|
216
|
-
it('trims a SURROUNDING newline rather than rejecting, and renders the trimmed name', () => {
|
|
217
|
-
// The guard runs on the trimmed name and the trimmed name is what gets
|
|
218
|
-
// returned — so a stray edge newline can never reach a rendered line.
|
|
219
|
-
const p = validatePrincipal({ kind: 'user', email: 'a@b.com', displayName: '\n Ali \n' });
|
|
220
|
-
expect(p.kind === 'user' && p.displayName).toBe('Ali');
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
it('rejects a plugin name with a colon (would forge a key)', () => {
|
|
224
|
-
expect(() => validatePrincipal({ kind: 'role', role: 'write:\n - everyone' })).toThrow(
|
|
225
|
-
AccessSpliceError,
|
|
226
|
-
);
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
it('rejects the reserved plugin name deny', () => {
|
|
230
|
-
expect(() => validatePrincipal({ kind: 'role', role: 'deny' })).toThrow(AccessSpliceError);
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
it('accepts the built-in everyone role (grantable as public read; route gates the verb)', () => {
|
|
234
|
-
expect(validatePrincipal({ kind: 'role', role: 'everyone' })).toEqual({
|
|
235
|
-
kind: 'role',
|
|
236
|
-
role: 'everyone',
|
|
237
|
-
});
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
it('rejects `#` in a name (uncomment() would truncate it on read-back)', () => {
|
|
241
|
-
expect(() =>
|
|
242
|
-
validatePrincipal({ kind: 'user', email: 'a@b.com', displayName: 'Felix #1' }),
|
|
243
|
-
).toThrow(AccessSpliceError);
|
|
244
|
-
expect(() => validatePrincipal({ kind: 'role', role: 'Team #1' })).toThrow(AccessSpliceError);
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
it('canonicalises a valid principal (lowercased email)', () => {
|
|
248
|
-
const p = validatePrincipal({ kind: 'user', email: 'Felix@Example.COM', displayName: 'Felix' });
|
|
249
|
-
expect(p.kind === 'user' && p.email).toBe('felix@example.com');
|
|
250
|
-
});
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
describe('round-trip — every granted entry parses back', () => {
|
|
254
|
-
it('a granted person renders to a line parseAccessEntry accepts', () => {
|
|
255
|
-
const r = spliceGrant(ROOT_ACCESS_MD, 'write', felix);
|
|
256
|
-
const line = r.text
|
|
257
|
-
.split('\n')
|
|
258
|
-
.find((l) => l.trim().startsWith('- Felix'))!
|
|
259
|
-
.trim()
|
|
260
|
-
.slice(2)
|
|
261
|
-
.trim();
|
|
262
|
-
const parsed = parseAccessEntry(line);
|
|
263
|
-
expect(parsed.ok).toBe(true);
|
|
264
|
-
expect(parsed.ok && parsed.entry.kind === 'user' && parsed.entry.email).toBe(
|
|
265
|
-
canonicalEmail(felix.kind === 'user' ? felix.email : ''),
|
|
266
|
-
);
|
|
267
|
-
});
|
|
268
|
-
});
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
spliceGrant,
|
|
5
|
+
spliceRevoke,
|
|
6
|
+
validatePrincipal,
|
|
7
|
+
AccessSpliceError,
|
|
8
|
+
type Principal,
|
|
9
|
+
} from '../access-splice.js';
|
|
10
|
+
import { parseAccessEntry, canonicalEmail } from '../access-grammar.js';
|
|
11
|
+
|
|
12
|
+
const felix: Principal = { kind: 'user', email: 'felix.kissel@example.com', displayName: 'Felix' };
|
|
13
|
+
const juan: Principal = { kind: 'user', email: 'juan@bevel.software', displayName: 'Juan V' };
|
|
14
|
+
const productTeam: Principal = { kind: 'role', role: 'Product Team' };
|
|
15
|
+
|
|
16
|
+
/** A realistic access.md with a prose body + comment — the thing parse→emit destroys. */
|
|
17
|
+
const ROOT_ACCESS_MD = `---
|
|
18
|
+
write:
|
|
19
|
+
- Admin
|
|
20
|
+
# download is independent of write
|
|
21
|
+
download:
|
|
22
|
+
- Juan <juan@bevel.software>
|
|
23
|
+
owner: []
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
# Repository access
|
|
27
|
+
|
|
28
|
+
This file is the root of the access-control tree. It grants write access only
|
|
29
|
+
to the \`Admin\` role by default. Subfolders can broaden access.
|
|
30
|
+
|
|
31
|
+
See \`lib/access-control.js\` for the resolution semantics.
|
|
32
|
+
`;
|
|
33
|
+
|
|
34
|
+
describe('spliceGrant — body + comment preservation (keystone)', () => {
|
|
35
|
+
it('keeps the markdown body, comments, and key order byte-for-byte', () => {
|
|
36
|
+
const r = spliceGrant(ROOT_ACCESS_MD, 'write', felix);
|
|
37
|
+
expect(r.changed).toBe(true);
|
|
38
|
+
// The body prose must survive verbatim.
|
|
39
|
+
expect(r.text).toContain('# Repository access');
|
|
40
|
+
expect(r.text).toContain('See `lib/access-control.js` for the resolution semantics.');
|
|
41
|
+
// The comment must survive.
|
|
42
|
+
expect(r.text).toContain('# download is independent of write');
|
|
43
|
+
// Existing entries untouched, new one appended under write.
|
|
44
|
+
expect(r.text).toContain(' - Admin');
|
|
45
|
+
expect(r.text).toContain(' - Felix <felix.kissel@example.com>');
|
|
46
|
+
// owner stays empty-list.
|
|
47
|
+
expect(r.text).toContain('owner: []');
|
|
48
|
+
// Felix lands under write, immediately after Admin, before the comment.
|
|
49
|
+
const lines = r.text.split('\n');
|
|
50
|
+
const adminIdx = lines.indexOf(' - Admin');
|
|
51
|
+
const felixIdx = lines.indexOf(' - Felix <felix.kissel@example.com>');
|
|
52
|
+
const commentIdx = lines.indexOf('# download is independent of write');
|
|
53
|
+
expect(adminIdx).toBeGreaterThan(-1);
|
|
54
|
+
expect(felixIdx).toBe(adminIdx + 1);
|
|
55
|
+
expect(commentIdx).toBeGreaterThan(felixIdx);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('grant then revoke returns to a body-preserving state', () => {
|
|
59
|
+
const granted = spliceGrant(ROOT_ACCESS_MD, 'write', felix).text;
|
|
60
|
+
const revoked = spliceRevoke(granted, 'write', felix).text;
|
|
61
|
+
expect(revoked).toContain('# Repository access');
|
|
62
|
+
expect(revoked).toContain(' - Admin');
|
|
63
|
+
expect(revoked).not.toContain('Felix');
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe('spliceGrant — idempotency + creation', () => {
|
|
68
|
+
it('is a no-op when the principal already has the grant', () => {
|
|
69
|
+
const once = spliceGrant(ROOT_ACCESS_MD, 'write', felix).text;
|
|
70
|
+
const twice = spliceGrant(once, 'write', felix);
|
|
71
|
+
expect(twice.changed).toBe(false);
|
|
72
|
+
expect(twice.text).toBe(once);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('creates the verb key when absent', () => {
|
|
76
|
+
const r = spliceGrant(ROOT_ACCESS_MD, 'read', productTeam);
|
|
77
|
+
expect(r.changed).toBe(true);
|
|
78
|
+
expect(r.text).toContain('read:');
|
|
79
|
+
expect(r.text).toContain(' - Product Team');
|
|
80
|
+
expect(r.text).toContain('# Repository access'); // body still there
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('promotes an inline-empty `owner: []` to a block list', () => {
|
|
84
|
+
const r = spliceGrant(ROOT_ACCESS_MD, 'owner', juan);
|
|
85
|
+
expect(r.changed).toBe(true);
|
|
86
|
+
expect(r.text).toContain('owner:\n - Juan V <juan@bevel.software>');
|
|
87
|
+
expect(r.text).not.toContain('owner: []');
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('synthesises frontmatter for a fresh (frontmatter-less) file', () => {
|
|
91
|
+
const r = spliceGrant('', 'write', felix);
|
|
92
|
+
expect(r.changed).toBe(true);
|
|
93
|
+
expect(r.text.startsWith('---\n')).toBe(true);
|
|
94
|
+
expect(r.text).toContain('write:\n - Felix <felix.kissel@example.com>');
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe('spliceRevoke', () => {
|
|
99
|
+
it('removes one entry and collapses the verb to [] when it empties', () => {
|
|
100
|
+
const r = spliceRevoke(ROOT_ACCESS_MD, 'write', { kind: 'role', role: 'Admin' });
|
|
101
|
+
expect(r.changed).toBe(true);
|
|
102
|
+
expect(r.text).toContain('write: []');
|
|
103
|
+
expect(r.text).not.toMatch(/- Admin/);
|
|
104
|
+
expect(r.text).toContain('# Repository access');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('is a no-op when the principal is not present', () => {
|
|
108
|
+
const r = spliceRevoke(ROOT_ACCESS_MD, 'write', felix);
|
|
109
|
+
expect(r.changed).toBe(false);
|
|
110
|
+
expect(r.text).toBe(ROOT_ACCESS_MD);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
describe('spliceGrant — verbs are independent (a principal may hold several)', () => {
|
|
115
|
+
it('grants a second verb without removing the principal from the first', () => {
|
|
116
|
+
// Verbs do not nest: granting `download` on top of `write` leaves BOTH.
|
|
117
|
+
const granted = spliceGrant(ROOT_ACCESS_MD, 'write', felix).text;
|
|
118
|
+
const both = spliceGrant(granted, 'download', felix).text;
|
|
119
|
+
const writeBlock = both.slice(both.indexOf('write:'), both.indexOf('download:'));
|
|
120
|
+
expect(writeBlock).toContain('Felix'); // still under write
|
|
121
|
+
expect(both).toContain('download:'); // and now under download too
|
|
122
|
+
const downloadBlock = both.slice(both.indexOf('download:'));
|
|
123
|
+
expect(downloadBlock).toContain('Felix');
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('is idempotent for an already-granted verb', () => {
|
|
127
|
+
const granted = spliceGrant(ROOT_ACCESS_MD, 'owner', felix).text;
|
|
128
|
+
const again = spliceGrant(granted, 'owner', felix);
|
|
129
|
+
expect(again.changed).toBe(false);
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
describe('spliceGrant({ deny }) — the deny-here per-item override', () => {
|
|
134
|
+
it('adds a `deny <principal>` line to an existing block list', () => {
|
|
135
|
+
// `write:` already lists Admin; deny Felix there.
|
|
136
|
+
const r = spliceGrant(ROOT_ACCESS_MD, 'write', felix, { deny: true });
|
|
137
|
+
expect(r.changed).toBe(true);
|
|
138
|
+
const writeBlock = r.text.slice(r.text.indexOf('write:'), r.text.indexOf('download:'));
|
|
139
|
+
expect(writeBlock).toContain('- deny Felix <felix.kissel@example.com>');
|
|
140
|
+
expect(writeBlock).toContain('- Admin'); // existing grant untouched
|
|
141
|
+
// The denial parses back as a deny entry.
|
|
142
|
+
const entry = parseAccessEntry('deny Felix <felix.kissel@example.com>');
|
|
143
|
+
expect(entry.ok && entry.entry.deny).toBe(true);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('promotes a node-frontmatter scalar to a block list with the deny entry', () => {
|
|
147
|
+
// A node whose own frontmatter names a sole owner via the scalar form.
|
|
148
|
+
const node = `---\nnodeType: process\nowner: Juan V <juan@bevel.software>\n---\n# Body\n`;
|
|
149
|
+
const r = spliceGrant(node, 'owner', felix, { deny: true, allowScalar: true });
|
|
150
|
+
expect(r.changed).toBe(true);
|
|
151
|
+
expect(r.text).toContain('owner:');
|
|
152
|
+
expect(r.text).toContain('- Juan V <juan@bevel.software>'); // old scalar preserved
|
|
153
|
+
expect(r.text).toContain('- deny Felix <felix.kissel@example.com>'); // deny added
|
|
154
|
+
expect(r.text).toContain('# Body'); // body survives
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('fills an inline-empty `owner: []` with the deny entry', () => {
|
|
158
|
+
const r = spliceGrant(ROOT_ACCESS_MD, 'owner', felix, { deny: true });
|
|
159
|
+
expect(r.changed).toBe(true);
|
|
160
|
+
const ownerBlock = r.text.slice(r.text.indexOf('owner:'));
|
|
161
|
+
expect(ownerBlock).toContain('- deny Felix <felix.kissel@example.com>');
|
|
162
|
+
expect(ownerBlock).not.toContain('owner: []'); // the [] collapsed to a real list
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('is a no-op when the same deny entry already exists', () => {
|
|
166
|
+
const once = spliceGrant(ROOT_ACCESS_MD, 'write', felix, { deny: true }).text;
|
|
167
|
+
const twice = spliceGrant(once, 'write', felix, { deny: true });
|
|
168
|
+
expect(twice.changed).toBe(false);
|
|
169
|
+
expect(twice.text).toBe(once);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('a deny and a grant for the same principal are independent (deny does not no-op a grant)', () => {
|
|
173
|
+
// An existing grant must NOT make the deny idempotent, and vice-versa —
|
|
174
|
+
// the idempotency key includes deny-ness. (The resolver-level grant-beats-
|
|
175
|
+
// deny interaction is handled one layer up in AccessMutationService.)
|
|
176
|
+
const granted = spliceGrant(ROOT_ACCESS_MD, 'write', felix).text;
|
|
177
|
+
const denied = spliceGrant(granted, 'write', felix, { deny: true });
|
|
178
|
+
expect(denied.changed).toBe(true);
|
|
179
|
+
const writeBlock = denied.text.slice(denied.text.indexOf('write:'), denied.text.indexOf('download:'));
|
|
180
|
+
expect(writeBlock).toContain('- Felix <felix.kissel@example.com>');
|
|
181
|
+
expect(writeBlock).toContain('- deny Felix <felix.kissel@example.com>');
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
describe('validatePrincipal — injection guard', () => {
|
|
186
|
+
it('rejects a newline-bearing email (frontmatter injection)', () => {
|
|
187
|
+
expect(() =>
|
|
188
|
+
validatePrincipal({
|
|
189
|
+
kind: 'user',
|
|
190
|
+
email: 'x@y.com\nwrite:\n - everyone',
|
|
191
|
+
displayName: 'X',
|
|
192
|
+
}),
|
|
193
|
+
).toThrow(AccessSpliceError);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('rejects a display name containing angle brackets', () => {
|
|
197
|
+
expect(() =>
|
|
198
|
+
validatePrincipal({ kind: 'user', email: 'a@b.com', displayName: 'Bad <hack>' }),
|
|
199
|
+
).toThrow(AccessSpliceError);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it.each([['\n'], ['\r'], ['\r\n'], [''], ['']])(
|
|
203
|
+
'rejects a display name carrying the control char %j (frontmatter injection)',
|
|
204
|
+
(ch) => {
|
|
205
|
+
expect(() =>
|
|
206
|
+
validatePrincipal({
|
|
207
|
+
kind: 'user',
|
|
208
|
+
email: 'a@b.com',
|
|
209
|
+
// Interior, so `trim()` cannot quietly rescue it on the way through.
|
|
210
|
+
displayName: `Ali${ch}read:${ch} - everyone`,
|
|
211
|
+
}),
|
|
212
|
+
).toThrow(AccessSpliceError);
|
|
213
|
+
},
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
it('trims a SURROUNDING newline rather than rejecting, and renders the trimmed name', () => {
|
|
217
|
+
// The guard runs on the trimmed name and the trimmed name is what gets
|
|
218
|
+
// returned — so a stray edge newline can never reach a rendered line.
|
|
219
|
+
const p = validatePrincipal({ kind: 'user', email: 'a@b.com', displayName: '\n Ali \n' });
|
|
220
|
+
expect(p.kind === 'user' && p.displayName).toBe('Ali');
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it('rejects a plugin name with a colon (would forge a key)', () => {
|
|
224
|
+
expect(() => validatePrincipal({ kind: 'role', role: 'write:\n - everyone' })).toThrow(
|
|
225
|
+
AccessSpliceError,
|
|
226
|
+
);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it('rejects the reserved plugin name deny', () => {
|
|
230
|
+
expect(() => validatePrincipal({ kind: 'role', role: 'deny' })).toThrow(AccessSpliceError);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it('accepts the built-in everyone role (grantable as public read; route gates the verb)', () => {
|
|
234
|
+
expect(validatePrincipal({ kind: 'role', role: 'everyone' })).toEqual({
|
|
235
|
+
kind: 'role',
|
|
236
|
+
role: 'everyone',
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it('rejects `#` in a name (uncomment() would truncate it on read-back)', () => {
|
|
241
|
+
expect(() =>
|
|
242
|
+
validatePrincipal({ kind: 'user', email: 'a@b.com', displayName: 'Felix #1' }),
|
|
243
|
+
).toThrow(AccessSpliceError);
|
|
244
|
+
expect(() => validatePrincipal({ kind: 'role', role: 'Team #1' })).toThrow(AccessSpliceError);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it('canonicalises a valid principal (lowercased email)', () => {
|
|
248
|
+
const p = validatePrincipal({ kind: 'user', email: 'Felix@Example.COM', displayName: 'Felix' });
|
|
249
|
+
expect(p.kind === 'user' && p.email).toBe('felix@example.com');
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
describe('round-trip — every granted entry parses back', () => {
|
|
254
|
+
it('a granted person renders to a line parseAccessEntry accepts', () => {
|
|
255
|
+
const r = spliceGrant(ROOT_ACCESS_MD, 'write', felix);
|
|
256
|
+
const line = r.text
|
|
257
|
+
.split('\n')
|
|
258
|
+
.find((l) => l.trim().startsWith('- Felix'))!
|
|
259
|
+
.trim()
|
|
260
|
+
.slice(2)
|
|
261
|
+
.trim();
|
|
262
|
+
const parsed = parseAccessEntry(line);
|
|
263
|
+
expect(parsed.ok).toBe(true);
|
|
264
|
+
expect(parsed.ok && parsed.entry.kind === 'user' && parsed.entry.email).toBe(
|
|
265
|
+
canonicalEmail(felix.kind === 'user' ? felix.email : ''),
|
|
266
|
+
);
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
269
|
|
|
270
270
|
describe('token-kind family — exact-token grants, shadow-aware revokes', () => {
|
|
271
271
|
// One name, two spellings, two possible principals: the bare token is the
|