@bevel-software/platform-core-backend 0.33.0 → 0.35.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 +146 -289
- package/agent-guide/conventions.md +23 -25
- package/agent-guide/directory-structure.md +72 -120
- package/agent-guide/drafts-and-change-requests.md +92 -0
- package/agent-guide/for-git-users.md +47 -0
- package/agent-guide/glossary.md +36 -0
- package/agent-guide/how-agents-find-things.md +49 -0
- package/agent-guide/html-views.md +239 -8
- package/agent-guide/new-knowledge-base.md +9 -8
- package/agent-guide/skills.md +67 -42
- package/agent-guide/start-here.md +65 -0
- package/agent-guide/tool-manuals.md +133 -155
- package/agent-guide/tools.md +67 -0
- package/agent-guide/where-a-new-file-goes.md +45 -28
- package/dist/core/catalog-revision.d.ts +12 -1
- package/dist/core/catalog-revision.d.ts.map +1 -1
- package/dist/core/catalog-revision.js +23 -3
- package/dist/core/catalog-revision.js.map +1 -1
- package/dist/core/core-ports.d.ts +41 -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 +15 -8
- package/dist/core/create-core-server.js.map +1 -1
- package/dist/core/create-core-services.d.ts +22 -5
- package/dist/core/create-core-services.d.ts.map +1 -1
- package/dist/core/create-core-services.js +44 -16
- package/dist/core/create-core-services.js.map +1 -1
- package/dist/core/lifecycle.d.ts +16 -0
- package/dist/core/lifecycle.d.ts.map +1 -1
- package/dist/core/lifecycle.js +12 -2
- package/dist/core/lifecycle.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/modules/access/access-control.interface.d.ts +39 -9
- package/dist/modules/access/access-control.interface.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.d.ts +15 -3
- package/dist/modules/access/access-control.service.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.js +239 -75
- package/dist/modules/access/access-control.service.js.map +1 -1
- package/dist/modules/access/access-mutation.service.d.ts +22 -24
- package/dist/modules/access/access-mutation.service.d.ts.map +1 -1
- package/dist/modules/access/access-mutation.service.js +48 -51
- package/dist/modules/access/access-mutation.service.js.map +1 -1
- package/dist/modules/access/access-view.d.ts.map +1 -1
- package/dist/modules/access/access-view.js +12 -11
- package/dist/modules/access/access-view.js.map +1 -1
- package/dist/modules/access/access.routes.d.ts +3 -0
- package/dist/modules/access/access.routes.d.ts.map +1 -1
- package/dist/modules/access/access.routes.js +58 -33
- package/dist/modules/access/access.routes.js.map +1 -1
- package/dist/modules/access/groups-admin.routes.d.ts.map +1 -1
- package/dist/modules/access/groups-admin.routes.js +4 -2
- package/dist/modules/access/groups-admin.routes.js.map +1 -1
- package/dist/modules/access/groups-admin.service.d.ts +44 -13
- package/dist/modules/access/groups-admin.service.d.ts.map +1 -1
- package/dist/modules/access/groups-admin.service.js +80 -38
- package/dist/modules/access/groups-admin.service.js.map +1 -1
- package/dist/modules/access/groups-edit.d.ts +1 -1
- package/dist/modules/access/groups-edit.d.ts.map +1 -1
- package/dist/modules/access/groups-edit.js +1 -1
- package/dist/modules/access/groups-edit.js.map +1 -1
- package/dist/modules/access/index.d.ts +1 -1
- package/dist/modules/access/index.d.ts.map +1 -1
- package/dist/modules/access/reference-scan.d.ts +116 -24
- package/dist/modules/access/reference-scan.d.ts.map +1 -1
- package/dist/modules/access/reference-scan.js +305 -44
- package/dist/modules/access/reference-scan.js.map +1 -1
- package/dist/modules/access/roles-admin.service.d.ts +17 -8
- package/dist/modules/access/roles-admin.service.d.ts.map +1 -1
- package/dist/modules/access/roles-admin.service.js +32 -23
- package/dist/modules/access/roles-admin.service.js.map +1 -1
- package/dist/modules/access-model/access-grammar.d.ts +35 -10
- package/dist/modules/access-model/access-grammar.d.ts.map +1 -1
- package/dist/modules/access-model/access-grammar.js +60 -20
- package/dist/modules/access-model/access-grammar.js.map +1 -1
- package/dist/modules/access-model/access-splice.d.ts +22 -18
- package/dist/modules/access-model/access-splice.d.ts.map +1 -1
- package/dist/modules/access-model/access-splice.js +20 -14
- package/dist/modules/access-model/access-splice.js.map +1 -1
- package/dist/modules/access-model/group-files.d.ts +1 -0
- package/dist/modules/access-model/group-files.d.ts.map +1 -1
- package/dist/modules/access-model/group-files.js +5 -1
- package/dist/modules/access-model/group-files.js.map +1 -1
- package/dist/modules/agent-guide/agent-guide.d.ts +47 -7
- package/dist/modules/agent-guide/agent-guide.d.ts.map +1 -1
- package/dist/modules/agent-guide/agent-guide.js +103 -13
- package/dist/modules/agent-guide/agent-guide.js.map +1 -1
- package/dist/modules/agent-guide/agent-guide.tools.d.ts +6 -2
- package/dist/modules/agent-guide/agent-guide.tools.d.ts.map +1 -1
- package/dist/modules/agent-guide/agent-guide.tools.js +46 -22
- package/dist/modules/agent-guide/agent-guide.tools.js.map +1 -1
- package/dist/modules/agent-guide/guide-budget.d.ts +48 -0
- package/dist/modules/agent-guide/guide-budget.d.ts.map +1 -0
- package/dist/modules/agent-guide/guide-budget.js +67 -0
- package/dist/modules/agent-guide/guide-budget.js.map +1 -0
- package/dist/modules/agent-guide/index.d.ts +2 -1
- package/dist/modules/agent-guide/index.d.ts.map +1 -1
- package/dist/modules/agent-guide/index.js +2 -1
- package/dist/modules/agent-guide/index.js.map +1 -1
- package/dist/modules/agent-instructions/compose.d.ts +3 -1
- package/dist/modules/agent-instructions/compose.d.ts.map +1 -1
- package/dist/modules/agent-instructions/compose.js +7 -1
- package/dist/modules/agent-instructions/compose.js.map +1 -1
- package/dist/modules/agent-instructions/shared-file-rules.d.ts +5 -1
- package/dist/modules/agent-instructions/shared-file-rules.d.ts.map +1 -1
- package/dist/modules/agent-instructions/shared-file-rules.js +26 -9
- package/dist/modules/agent-instructions/shared-file-rules.js.map +1 -1
- package/dist/modules/agent-scope/agent-scope.contract.d.ts +67 -0
- package/dist/modules/agent-scope/agent-scope.contract.d.ts.map +1 -0
- package/dist/modules/agent-scope/agent-scope.contract.js +27 -0
- package/dist/modules/agent-scope/agent-scope.contract.js.map +1 -0
- package/dist/modules/agent-scope/connection-scope.d.ts +111 -0
- package/dist/modules/agent-scope/connection-scope.d.ts.map +1 -0
- package/dist/modules/agent-scope/connection-scope.js +165 -0
- package/dist/modules/agent-scope/connection-scope.js.map +1 -0
- package/dist/modules/agent-scope/index.d.ts +3 -0
- package/dist/modules/agent-scope/index.d.ts.map +1 -0
- package/dist/modules/agent-scope/index.js +3 -0
- package/dist/modules/agent-scope/index.js.map +1 -0
- package/dist/modules/audit/agent-audit.service.d.ts.map +1 -1
- package/dist/modules/audit/agent-audit.service.js +2 -0
- package/dist/modules/audit/agent-audit.service.js.map +1 -1
- package/dist/modules/audit/audit.contract.d.ts +6 -0
- package/dist/modules/audit/audit.contract.d.ts.map +1 -1
- package/dist/modules/audit/audit.contract.js.map +1 -1
- package/dist/modules/auth/account-admission.d.ts +12 -0
- package/dist/modules/auth/account-admission.d.ts.map +1 -1
- package/dist/modules/auth/account-admission.js +15 -0
- package/dist/modules/auth/account-admission.js.map +1 -1
- package/dist/modules/auth/auth.middleware.d.ts +27 -1
- package/dist/modules/auth/auth.middleware.d.ts.map +1 -1
- package/dist/modules/auth/auth.middleware.js +42 -4
- package/dist/modules/auth/auth.middleware.js.map +1 -1
- package/dist/modules/auth/auth.routes.d.ts +13 -9
- package/dist/modules/auth/auth.routes.d.ts.map +1 -1
- package/dist/modules/auth/auth.routes.js +35 -29
- package/dist/modules/auth/auth.routes.js.map +1 -1
- package/dist/modules/auth/auth.service.d.ts +55 -6
- package/dist/modules/auth/auth.service.d.ts.map +1 -1
- package/dist/modules/auth/auth.service.js +128 -34
- package/dist/modules/auth/auth.service.js.map +1 -1
- package/dist/modules/auth/index.d.ts +1 -1
- package/dist/modules/auth/index.d.ts.map +1 -1
- package/dist/modules/auth/index.js +1 -1
- package/dist/modules/auth/index.js.map +1 -1
- package/dist/modules/auth/oidc-auth-provider.d.ts.map +1 -1
- package/dist/modules/auth/oidc-auth-provider.js +2 -9
- package/dist/modules/auth/oidc-auth-provider.js.map +1 -1
- package/dist/modules/code-mode/code-mode.tool.js +2 -2
- package/dist/modules/code-mode/code-mode.tool.js.map +1 -1
- package/dist/modules/connection-probe/connection-probe.service.d.ts +0 -12
- package/dist/modules/connection-probe/connection-probe.service.d.ts.map +1 -1
- package/dist/modules/connection-probe/connection-probe.service.js +1 -38
- package/dist/modules/connection-probe/connection-probe.service.js.map +1 -1
- package/dist/modules/database/core-schema.d.ts +68 -0
- package/dist/modules/database/core-schema.d.ts.map +1 -1
- package/dist/modules/database/core-schema.js +33 -0
- package/dist/modules/database/core-schema.js.map +1 -1
- package/dist/modules/declared-variables/declared-variables.routes.d.ts +8 -1
- package/dist/modules/declared-variables/declared-variables.routes.d.ts.map +1 -1
- package/dist/modules/declared-variables/declared-variables.routes.js +13 -2
- package/dist/modules/declared-variables/declared-variables.routes.js.map +1 -1
- package/dist/modules/embed/embed.tools.d.ts.map +1 -1
- package/dist/modules/embed/embed.tools.js +5 -0
- package/dist/modules/embed/embed.tools.js.map +1 -1
- package/dist/modules/kb-fs/index.d.ts +2 -0
- package/dist/modules/kb-fs/index.d.ts.map +1 -1
- package/dist/modules/kb-fs/index.js +1 -0
- package/dist/modules/kb-fs/index.js.map +1 -1
- package/dist/modules/kb-fs/locking-filesystem.d.ts.map +1 -1
- package/dist/modules/kb-fs/locking-filesystem.js +7 -1
- package/dist/modules/kb-fs/locking-filesystem.js.map +1 -1
- package/dist/modules/kb-fs/repo-path.d.ts +26 -6
- package/dist/modules/kb-fs/repo-path.d.ts.map +1 -1
- package/dist/modules/kb-fs/repo-path.js +22 -7
- package/dist/modules/kb-fs/repo-path.js.map +1 -1
- package/dist/modules/kb-fs/tmp-ref.d.ts +36 -0
- package/dist/modules/kb-fs/tmp-ref.d.ts.map +1 -0
- package/dist/modules/kb-fs/tmp-ref.js +76 -0
- package/dist/modules/kb-fs/tmp-ref.js.map +1 -0
- package/dist/modules/mcp/mcp-auth.middleware.d.ts +7 -0
- package/dist/modules/mcp/mcp-auth.middleware.d.ts.map +1 -1
- package/dist/modules/mcp/mcp-auth.middleware.js +14 -1
- package/dist/modules/mcp/mcp-auth.middleware.js.map +1 -1
- package/dist/modules/mcp/mcp.routes.d.ts +7 -1
- package/dist/modules/mcp/mcp.routes.d.ts.map +1 -1
- package/dist/modules/mcp/mcp.routes.js +39 -4
- package/dist/modules/mcp/mcp.routes.js.map +1 -1
- package/dist/modules/mcp/mcp.service.d.ts +11 -0
- package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
- package/dist/modules/mcp/mcp.service.js +74 -48
- package/dist/modules/mcp/mcp.service.js.map +1 -1
- package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts +24 -1
- package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts.map +1 -1
- package/dist/modules/mcp/oauth/bevel-oauth-provider.js +45 -10
- package/dist/modules/mcp/oauth/bevel-oauth-provider.js.map +1 -1
- package/dist/modules/mcp/oauth/oauth-consent.routes.d.ts +9 -0
- package/dist/modules/mcp/oauth/oauth-consent.routes.d.ts.map +1 -1
- package/dist/modules/mcp/oauth/oauth-consent.routes.js +34 -1
- package/dist/modules/mcp/oauth/oauth-consent.routes.js.map +1 -1
- package/dist/modules/plugins/index.d.ts +1 -1
- package/dist/modules/plugins/index.d.ts.map +1 -1
- package/dist/modules/plugins/index.js.map +1 -1
- package/dist/modules/plugins/plugin-links.d.ts +18 -2
- package/dist/modules/plugins/plugin-links.d.ts.map +1 -1
- package/dist/modules/plugins/plugin-links.js.map +1 -1
- package/dist/modules/plugins/plugins.routes.d.ts +5 -1
- package/dist/modules/plugins/plugins.routes.d.ts.map +1 -1
- package/dist/modules/plugins/plugins.routes.js +32 -5
- package/dist/modules/plugins/plugins.routes.js.map +1 -1
- package/dist/modules/secrets-vault/db-secrets-vault.service.d.ts +11 -1
- package/dist/modules/secrets-vault/db-secrets-vault.service.d.ts.map +1 -1
- package/dist/modules/secrets-vault/db-secrets-vault.service.js +159 -24
- package/dist/modules/secrets-vault/db-secrets-vault.service.js.map +1 -1
- package/dist/modules/secrets-vault/index.d.ts +1 -1
- package/dist/modules/secrets-vault/index.d.ts.map +1 -1
- package/dist/modules/secrets-vault/index.js +1 -1
- package/dist/modules/secrets-vault/index.js.map +1 -1
- package/dist/modules/secrets-vault/secrets-vault.contract.d.ts +53 -11
- package/dist/modules/secrets-vault/secrets-vault.contract.d.ts.map +1 -1
- package/dist/modules/secrets-vault/secrets-vault.contract.js +27 -0
- package/dist/modules/secrets-vault/secrets-vault.contract.js.map +1 -1
- package/dist/modules/secrets-vault/secrets-vault.routes.d.ts +9 -0
- package/dist/modules/secrets-vault/secrets-vault.routes.d.ts.map +1 -1
- package/dist/modules/secrets-vault/secrets-vault.routes.js +100 -42
- package/dist/modules/secrets-vault/secrets-vault.routes.js.map +1 -1
- package/dist/modules/settings/oidc-check.d.ts.map +1 -1
- package/dist/modules/settings/oidc-check.js +5 -17
- package/dist/modules/settings/oidc-check.js.map +1 -1
- package/dist/modules/skills/index.d.ts +3 -3
- package/dist/modules/skills/index.d.ts.map +1 -1
- package/dist/modules/skills/index.js +0 -2
- package/dist/modules/skills/index.js.map +1 -1
- package/dist/modules/skills/skills.contract.d.ts +16 -0
- package/dist/modules/skills/skills.contract.d.ts.map +1 -1
- package/dist/modules/skills/skills.service.d.ts.map +1 -1
- package/dist/modules/skills/skills.service.js +6 -1
- package/dist/modules/skills/skills.service.js.map +1 -1
- package/dist/modules/skills/skills.tools.d.ts.map +1 -1
- package/dist/modules/skills/skills.tools.js +25 -13
- package/dist/modules/skills/skills.tools.js.map +1 -1
- package/dist/modules/tool-auth/external-api-key.interface.d.ts +11 -0
- 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 +4 -1
- package/dist/modules/tool-auth/external-api-key.service.js.map +1 -1
- package/dist/modules/tool-auth/index.d.ts +1 -1
- package/dist/modules/tool-auth/index.d.ts.map +1 -1
- package/dist/modules/tool-auth/internal-token.service.d.ts +9 -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 +27 -3
- package/dist/modules/tool-auth/tool-auth.middleware.d.ts.map +1 -1
- package/dist/modules/tool-auth/tool-auth.middleware.js +32 -6
- package/dist/modules/tool-auth/tool-auth.middleware.js.map +1 -1
- package/dist/modules/tool-helpers/tool-context.d.ts +7 -0
- package/dist/modules/tool-helpers/tool-context.d.ts.map +1 -1
- package/dist/modules/tool-helpers/tool-context.js +3 -0
- package/dist/modules/tool-helpers/tool-context.js.map +1 -1
- package/dist/modules/tool-helpers/tool-def.js +1 -1
- package/dist/modules/tool-helpers/tool-def.js.map +1 -1
- package/dist/modules/tool-helpers/tool-handler.d.ts +15 -0
- package/dist/modules/tool-helpers/tool-handler.d.ts.map +1 -1
- package/dist/modules/tool-helpers/tool-handler.js +19 -0
- package/dist/modules/tool-helpers/tool-handler.js.map +1 -1
- package/dist/modules/tool-helpers/tool.contract.d.ts +10 -0
- package/dist/modules/tool-helpers/tool.contract.d.ts.map +1 -1
- package/dist/modules/tool-helpers/tool.contract.js.map +1 -1
- package/dist/modules/tool-helpers/validate-token.d.ts +3 -1
- package/dist/modules/tool-helpers/validate-token.d.ts.map +1 -1
- package/dist/modules/tool-helpers/validate-token.js +1 -1
- package/dist/modules/tool-helpers/validate-token.js.map +1 -1
- package/dist/modules/tool-manuals/index.d.ts +3 -3
- package/dist/modules/tool-manuals/index.d.ts.map +1 -1
- package/dist/modules/tool-manuals/index.js +0 -2
- package/dist/modules/tool-manuals/index.js.map +1 -1
- package/dist/modules/tool-manuals/mcp-json-discovery.d.ts +7 -1
- package/dist/modules/tool-manuals/mcp-json-discovery.d.ts.map +1 -1
- package/dist/modules/tool-manuals/mcp-json-discovery.js +69 -38
- package/dist/modules/tool-manuals/mcp-json-discovery.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.contract.d.ts +46 -6
- package/dist/modules/tool-manuals/tool-manuals.contract.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.routes.d.ts +7 -1
- package/dist/modules/tool-manuals/tool-manuals.routes.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.routes.js +41 -4
- package/dist/modules/tool-manuals/tool-manuals.routes.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.service.d.ts +24 -13
- package/dist/modules/tool-manuals/tool-manuals.service.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.service.js +138 -59
- package/dist/modules/tool-manuals/tool-manuals.service.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.tools.d.ts +8 -2
- package/dist/modules/tool-manuals/tool-manuals.tools.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.tools.js +56 -20
- package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
- package/dist/modules/tool-registry/manual.routes.d.ts +8 -1
- package/dist/modules/tool-registry/manual.routes.d.ts.map +1 -1
- package/dist/modules/tool-registry/manual.routes.js +27 -7
- package/dist/modules/tool-registry/manual.routes.js.map +1 -1
- package/dist/modules/tool-registry/tool.contract.d.ts +7 -0
- package/dist/modules/tool-registry/tool.contract.d.ts.map +1 -1
- package/dist/modules/tool-registry/tool.contract.js.map +1 -1
- package/dist/modules/workflow/agent-tools/workflow.tools.d.ts.map +1 -1
- package/dist/modules/workflow/agent-tools/workflow.tools.js +36 -27
- package/dist/modules/workflow/agent-tools/workflow.tools.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 +20 -35
- package/dist/modules/workflow/git/git.service.js.map +1 -1
- package/dist/modules/workflow/workflow.service.d.ts +2 -1
- package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
- package/dist/modules/workflow/workflow.service.js +3 -0
- package/dist/modules/workflow/workflow.service.js.map +1 -1
- package/dist/modules/workspace/agent-access.gate.d.ts +1 -1
- package/dist/modules/workspace/agent-access.gate.d.ts.map +1 -1
- package/dist/modules/workspace/agent-access.gate.js +1 -1
- package/dist/modules/workspace/agent-access.gate.js.map +1 -1
- package/dist/modules/workspace/file-readers/doc-extract.service.d.ts +28 -4
- package/dist/modules/workspace/file-readers/doc-extract.service.d.ts.map +1 -1
- package/dist/modules/workspace/file-readers/doc-extract.service.js +61 -40
- package/dist/modules/workspace/file-readers/doc-extract.service.js.map +1 -1
- package/dist/modules/workspace/index.d.ts +8 -0
- package/dist/modules/workspace/index.d.ts.map +1 -1
- package/dist/modules/workspace/index.js +8 -0
- package/dist/modules/workspace/index.js.map +1 -1
- package/dist/modules/workspace/nul-scan.d.ts +19 -0
- package/dist/modules/workspace/nul-scan.d.ts.map +1 -0
- package/dist/modules/workspace/nul-scan.js +55 -0
- package/dist/modules/workspace/nul-scan.js.map +1 -0
- package/dist/modules/workspace/pattern-matcher.contract.d.ts +110 -27
- package/dist/modules/workspace/pattern-matcher.contract.d.ts.map +1 -1
- package/dist/modules/workspace/pattern-matcher.contract.js +30 -13
- package/dist/modules/workspace/pattern-matcher.contract.js.map +1 -1
- package/dist/modules/workspace/ripgrep-search.d.ts +54 -0
- package/dist/modules/workspace/ripgrep-search.d.ts.map +1 -0
- package/dist/modules/workspace/ripgrep-search.js +657 -0
- package/dist/modules/workspace/ripgrep-search.js.map +1 -0
- package/dist/modules/workspace/ripgrep-version.d.ts +15 -0
- package/dist/modules/workspace/ripgrep-version.d.ts.map +1 -0
- package/dist/modules/workspace/ripgrep-version.js +31 -0
- package/dist/modules/workspace/ripgrep-version.js.map +1 -0
- package/dist/modules/workspace/startup/beside-checkout.d.ts +8 -5
- package/dist/modules/workspace/startup/beside-checkout.d.ts.map +1 -1
- package/dist/modules/workspace/startup/beside-checkout.js +14 -5
- package/dist/modules/workspace/startup/beside-checkout.js.map +1 -1
- package/dist/modules/workspace/worker-pool.d.ts +3 -4
- package/dist/modules/workspace/worker-pool.d.ts.map +1 -1
- package/dist/modules/workspace/worker-pool.js.map +1 -1
- package/dist/modules/workspace/workspace-tmp.d.ts +146 -0
- package/dist/modules/workspace/workspace-tmp.d.ts.map +1 -0
- package/dist/modules/workspace/workspace-tmp.js +435 -0
- package/dist/modules/workspace/workspace-tmp.js.map +1 -0
- package/dist/modules/workspace/workspace.routes.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.routes.js +5 -26
- package/dist/modules/workspace/workspace.routes.js.map +1 -1
- package/dist/modules/workspace/workspace.service.d.ts +47 -6
- package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.service.js +86 -6
- package/dist/modules/workspace/workspace.service.js.map +1 -1
- package/dist/modules/workspace/workspace.tools.d.ts +3 -2
- package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.tools.js +531 -137
- package/dist/modules/workspace/workspace.tools.js.map +1 -1
- package/dist/shared/oauth-token-error.d.ts +23 -0
- package/dist/shared/oauth-token-error.d.ts.map +1 -0
- package/dist/shared/oauth-token-error.js +47 -0
- package/dist/shared/oauth-token-error.js.map +1 -0
- package/dist/shared/path-order.d.ts +28 -8
- package/dist/shared/path-order.d.ts.map +1 -1
- package/dist/shared/path-order.js +37 -8
- package/dist/shared/path-order.js.map +1 -1
- package/dist/shared/read-capped.d.ts +19 -0
- package/dist/shared/read-capped.d.ts.map +1 -0
- package/dist/shared/read-capped.js +45 -0
- package/dist/shared/read-capped.js.map +1 -0
- package/migrations/0022_session_version.sql +6 -0
- package/migrations/0023_agent_path.sql +3 -0
- package/migrations/meta/0022_snapshot.json +2360 -0
- package/migrations/meta/0023_snapshot.json +2378 -0
- package/migrations/meta/_journal.json +14 -0
- package/package.json +4 -3
- package/src/__tests__/hexis-catalog.ts +144 -0
- package/src/__tests__/pg-scratch.test.ts +69 -0
- package/src/__tests__/pg-scratch.ts +31 -0
- package/src/__tests__/real-disk.ts +21 -0
- package/src/__tests__/slow-ripgrep.ts +65 -0
- package/src/__tests__/tokens.ts +19 -0
- package/src/__tests__/tool-manuals.ts +32 -0
- package/src/__tests__/tool-wiring.ts +25 -0
- package/src/core/__tests__/boot-without-ripgrep.test.ts +34 -0
- package/src/core/__tests__/catalog-revision.test.ts +89 -0
- package/src/core/__tests__/damaged-document.tools.test.ts +2 -2
- package/src/core/__tests__/gated-boot.test.ts +8 -1
- package/src/core/__tests__/lifecycle.test.ts +46 -13
- package/src/core/__tests__/oauth-sign-in-real-file.test.ts +179 -0
- package/src/core/catalog-revision.ts +24 -2
- package/src/core/core-ports.ts +42 -0
- package/src/core/create-core-server.ts +14 -1
- package/src/core/create-core-services.ts +62 -15
- package/src/core/lifecycle.ts +26 -2
- package/src/index.ts +1 -1
- package/src/modules/access/__tests__/access-control.admin-root-floor.test.ts +12 -0
- package/src/modules/access/__tests__/access-control.colliding-name-cases.test.ts +113 -0
- package/src/modules/access/__tests__/access-control.frontmatter-head.test.ts +117 -0
- package/src/modules/access/__tests__/access-control.group-named-admin.test.ts +122 -0
- package/src/modules/access/__tests__/access-control.preview-relocation.test.ts +50 -0
- package/src/modules/access/__tests__/access-declarations.test.ts +19 -0
- package/src/modules/access/__tests__/access-groups.test.ts +55 -22
- package/src/modules/access/__tests__/access-mutation.service.test.ts +85 -59
- package/src/modules/access/__tests__/access-restrict-here.test.ts +7 -5
- package/src/modules/access/__tests__/access.routes.admins.test.ts +1 -0
- package/src/modules/access/__tests__/access.routes.batch.test.ts +2 -0
- package/src/modules/access/__tests__/access.routes.folder-governs.test.ts +2 -0
- package/src/modules/access/__tests__/access.routes.group-grant.test.ts +50 -14
- package/src/modules/access/__tests__/access.routes.overrides.test.ts +2 -1
- package/src/modules/access/__tests__/access.routes.proposal-branch.test.ts +1 -0
- package/src/modules/access/__tests__/access.routes.prospective.test.ts +52 -10
- package/src/modules/access/__tests__/access.routes.revoke.test.ts +18 -13
- package/src/modules/access/__tests__/access.routes.unknown-email.test.ts +2 -0
- package/src/modules/access/__tests__/grant-sources.test.ts +20 -15
- package/src/modules/access/__tests__/groups-admin.routes.test.ts +61 -0
- package/src/modules/access/__tests__/groups-admin.service.test.ts +211 -18
- package/src/modules/access/__tests__/holder-changes.test.ts +127 -0
- package/src/modules/access/__tests__/reference-scan.test.ts +366 -14
- package/src/modules/access/__tests__/roles-admin.security.test.ts +1 -1
- package/src/modules/access/__tests__/roles-admin.service.test.ts +43 -22
- package/src/modules/access/__tests__/roles-capabilities.test.ts +6 -2
- package/src/modules/access/__tests__/roles.routes.test.ts +1 -1
- package/src/modules/access/access-control.interface.ts +43 -10
- package/src/modules/access/access-control.service.ts +250 -72
- package/src/modules/access/access-mutation.service.ts +48 -53
- package/src/modules/access/access-view.ts +12 -10
- package/src/modules/access/access.routes.ts +66 -37
- package/src/modules/access/groups-admin.routes.ts +4 -2
- package/src/modules/access/groups-admin.service.ts +114 -44
- package/src/modules/access/groups-edit.ts +1 -1
- package/src/modules/access/index.ts +1 -1
- package/src/modules/access/reference-scan.ts +319 -46
- package/src/modules/access/roles-admin.service.ts +43 -26
- package/src/modules/access/synced-groups-writer.ts +1 -1
- package/src/modules/access-model/__tests__/access-grammar.test.ts +38 -1
- package/src/modules/access-model/__tests__/access-splice.test.ts +50 -8
- package/src/modules/access-model/access-grammar.ts +65 -20
- package/src/modules/access-model/access-splice.ts +31 -23
- package/src/modules/access-model/group-files.ts +5 -0
- package/src/modules/agent-guide/__tests__/agent-guide.test.ts +110 -25
- package/src/modules/agent-guide/__tests__/agent-guide.tools.test.ts +38 -11
- package/src/modules/agent-guide/__tests__/guide-budget.test.ts +95 -0
- package/src/modules/agent-guide/__tests__/guide-eval.test.ts +182 -0
- package/src/modules/agent-guide/__tests__/guide-matches-code.test.ts +263 -0
- package/src/modules/agent-guide/__tests__/tool-surface.json +514 -0
- package/src/modules/agent-guide/__tests__/tool-surface.test.ts +75 -0
- package/src/modules/agent-guide/agent-guide.tools.ts +51 -22
- package/src/modules/agent-guide/agent-guide.ts +120 -13
- package/src/modules/agent-guide/guide-budget.ts +73 -0
- package/src/modules/agent-guide/index.ts +12 -0
- package/src/modules/agent-instructions/__tests__/shared-file-rules.test.ts +5 -4
- package/src/modules/agent-instructions/compose.ts +7 -1
- package/src/modules/agent-instructions/shared-file-rules.ts +27 -9
- package/src/modules/agent-scope/__tests__/agent-surfaces.connect-as.test.ts +421 -0
- package/src/modules/agent-scope/__tests__/connection-scope.test.ts +46 -0
- package/src/modules/agent-scope/__tests__/in-memory-kb.ts +148 -0
- package/src/modules/agent-scope/agent-scope.contract.ts +83 -0
- package/src/modules/agent-scope/connection-scope.ts +199 -0
- package/src/modules/agent-scope/index.ts +15 -0
- package/src/modules/audit/agent-audit.service.ts +2 -0
- package/src/modules/audit/audit.contract.ts +6 -0
- package/src/modules/auth/__tests__/auth.middleware.test.ts +30 -2
- package/src/modules/auth/__tests__/auth.routes.test.ts +71 -5
- package/src/modules/auth/__tests__/auth.service.test.ts +90 -9
- package/src/modules/auth/__tests__/password-change-sessions.pg.test.ts +269 -0
- package/src/modules/auth/account-admission.ts +19 -0
- package/src/modules/auth/auth.middleware.ts +45 -4
- package/src/modules/auth/auth.routes.ts +46 -29
- package/src/modules/auth/auth.service.ts +135 -34
- package/src/modules/auth/index.ts +2 -0
- package/src/modules/auth/oidc-auth-provider.ts +2 -9
- package/src/modules/code-mode/code-mode.tool.ts +2 -2
- package/src/modules/connection-probe/__tests__/connection-probe.service.test.ts +5 -24
- package/src/modules/connection-probe/connection-probe.service.ts +1 -34
- package/src/modules/database/__tests__/session-version-migration.pg.test.ts +133 -0
- package/src/modules/database/core-schema.ts +33 -0
- package/src/modules/declared-variables/__tests__/declared-variables.route.test.ts +44 -1
- package/src/modules/declared-variables/declared-variables.routes.ts +12 -1
- package/src/modules/embed/__tests__/embed.tools.test.ts +12 -0
- package/src/modules/embed/embed.tools.ts +5 -0
- package/src/modules/kb-fs/__tests__/locking-filesystem.test.ts +25 -0
- package/src/modules/kb-fs/__tests__/repo-path.test.ts +53 -8
- package/src/modules/kb-fs/index.ts +2 -0
- package/src/modules/kb-fs/locking-filesystem.ts +7 -1
- package/src/modules/kb-fs/repo-path.ts +33 -7
- package/src/modules/kb-fs/tmp-ref.ts +81 -0
- package/src/modules/mcp/__tests__/connect-as.pg.test.ts +137 -0
- package/src/modules/mcp/__tests__/mcp-auth.middleware.test.ts +29 -5
- package/src/modules/mcp/__tests__/mcp-routes-harness.ts +8 -0
- package/src/modules/mcp/__tests__/mcp.connect-as.test.ts +195 -0
- package/src/modules/mcp/__tests__/mcp.routes.connect-as.test.ts +192 -0
- package/src/modules/mcp/__tests__/oauth-consent.connect-as.test.ts +146 -0
- package/src/modules/mcp/mcp-auth.middleware.ts +19 -2
- package/src/modules/mcp/mcp.routes.ts +39 -3
- package/src/modules/mcp/mcp.service.ts +112 -52
- package/src/modules/mcp/oauth/bevel-oauth-provider.ts +46 -8
- package/src/modules/mcp/oauth/oauth-consent.routes.ts +41 -1
- package/src/modules/plugins/__tests__/plugins.tools.test.ts +38 -2
- package/src/modules/plugins/index.ts +1 -1
- package/src/modules/plugins/plugin-links.ts +19 -2
- package/src/modules/plugins/plugins.routes.ts +35 -4
- package/src/modules/secrets-vault/__tests__/connect-pending.route.test.ts +113 -0
- package/src/modules/secrets-vault/__tests__/db-secrets-vault.oauth.test.ts +430 -3
- package/src/modules/secrets-vault/__tests__/oauth-live-declaration.route.test.ts +218 -0
- package/src/modules/secrets-vault/__tests__/oauth-return-to.route.test.ts +39 -6
- package/src/modules/secrets-vault/db-secrets-vault.service.ts +170 -27
- package/src/modules/secrets-vault/index.ts +1 -1
- package/src/modules/secrets-vault/secrets-vault.contract.ts +61 -11
- package/src/modules/secrets-vault/secrets-vault.routes.ts +114 -42
- package/src/modules/settings/oidc-check.ts +5 -18
- package/src/modules/skills/__tests__/skills.service.test.ts +34 -0
- package/src/modules/skills/index.ts +7 -2
- package/src/modules/skills/skills.contract.ts +17 -0
- package/src/modules/skills/skills.service.ts +5 -1
- package/src/modules/skills/skills.tools.ts +45 -14
- package/src/modules/tool-auth/__tests__/external-api-key.service.test.ts +4 -0
- package/src/modules/tool-auth/external-api-key.interface.ts +11 -1
- package/src/modules/tool-auth/external-api-key.service.ts +5 -2
- package/src/modules/tool-auth/index.ts +1 -1
- package/src/modules/tool-auth/internal-token.service.ts +10 -0
- package/src/modules/tool-auth/tool-auth.middleware.ts +45 -3
- package/src/modules/tool-helpers/__tests__/agent-roles-write.test.ts +19 -11
- package/src/modules/tool-helpers/__tests__/can-write.tools.test.ts +3 -1
- package/src/modules/tool-helpers/__tests__/route-tool-schemas.catalog.test.ts +38 -0
- package/src/modules/tool-helpers/tool-context.ts +10 -0
- package/src/modules/tool-helpers/tool-def.ts +1 -1
- package/src/modules/tool-helpers/tool-handler.ts +34 -0
- package/src/modules/tool-helpers/tool.contract.ts +10 -0
- package/src/modules/tool-helpers/validate-token.ts +4 -2
- package/src/modules/tool-manuals/__tests__/tool-manuals.archive.route.test.ts +80 -4
- package/src/modules/tool-manuals/__tests__/tool-manuals.mcp-oauth.test.ts +197 -11
- package/src/modules/tool-manuals/__tests__/tool-manuals.service.test.ts +42 -0
- package/src/modules/tool-manuals/__tests__/tool-manuals.tools.test.ts +99 -8
- package/src/modules/tool-manuals/index.ts +6 -2
- package/src/modules/tool-manuals/mcp-json-discovery.ts +73 -34
- package/src/modules/tool-manuals/tool-manuals.contract.ts +40 -6
- package/src/modules/tool-manuals/tool-manuals.routes.ts +40 -3
- package/src/modules/tool-manuals/tool-manuals.service.ts +169 -61
- package/src/modules/tool-manuals/tool-manuals.tools.ts +81 -25
- package/src/modules/tool-registry/__tests__/tool-description-length.test.ts +6 -113
- package/src/modules/tool-registry/manual.routes.ts +26 -5
- package/src/modules/tool-registry/tool.contract.ts +7 -0
- package/src/modules/workflow/__tests__/workflow.service.facade.test.ts +1 -1
- package/src/modules/workflow/__tests__/workflow.service.sync.test.ts +1 -1
- package/src/modules/workflow/agent-tools/__tests__/workflow.tools.test.ts +54 -3
- package/src/modules/workflow/agent-tools/workflow.tools.ts +36 -27
- package/src/modules/workflow/git/__tests__/git.service.list-branches.test.ts +130 -3
- package/src/modules/workflow/git/git.service.ts +29 -48
- package/src/modules/workflow/workflow.service.ts +5 -0
- package/src/modules/workspace/__tests__/git-internals.security.test.ts +2 -2
- package/src/modules/workspace/__tests__/holds-nul.test.ts +66 -0
- package/src/modules/workspace/__tests__/rest-tool-arguments.test.ts +3 -3
- package/src/modules/workspace/__tests__/ripgrep-search.test.ts +570 -0
- package/src/modules/workspace/__tests__/ripgrep-version.test.ts +29 -0
- package/src/modules/workspace/__tests__/workspace-tmp.tools.test.ts +928 -0
- package/src/modules/workspace/__tests__/workspace.routes.read-gate.test.ts +17 -37
- package/src/modules/workspace/__tests__/workspace.service.read-filter.test.ts +20 -8
- package/src/modules/workspace/__tests__/workspace.tools.agents-file.test.ts +10 -12
- package/src/modules/workspace/__tests__/workspace.tools.branch-errors.test.ts +22 -10
- package/src/modules/workspace/__tests__/workspace.tools.test.ts +549 -162
- package/src/modules/workspace/agent-access.gate.ts +1 -1
- package/src/modules/workspace/file-readers/__tests__/doc-extract.service.test.ts +10 -0
- package/src/modules/workspace/file-readers/doc-extract.service.ts +71 -38
- package/src/modules/workspace/index.ts +20 -0
- package/src/modules/workspace/nul-scan.ts +50 -0
- package/src/modules/workspace/pattern-matcher.contract.ts +123 -32
- package/src/modules/workspace/ripgrep-search.ts +715 -0
- package/src/modules/workspace/ripgrep-version.ts +35 -0
- package/src/modules/workspace/startup/__tests__/beside-checkout.test.ts +11 -0
- package/src/modules/workspace/startup/beside-checkout.ts +13 -5
- package/src/modules/workspace/worker-pool.ts +3 -4
- package/src/modules/workspace/workspace-tmp.ts +457 -0
- package/src/modules/workspace/workspace.routes.ts +6 -27
- package/src/modules/workspace/workspace.service.ts +92 -15
- package/src/modules/workspace/workspace.tools.ts +574 -176
- package/src/shared/__tests__/read-capped.test.ts +68 -0
- package/src/shared/oauth-token-error.ts +46 -0
- package/src/shared/path-order.ts +37 -8
- package/src/shared/read-capped.ts +40 -0
- package/starter-packs/engineering/Plugins/engineering-starter/access.md +1 -1
- package/starter-packs/general/Plugins/general-starter/access.md +1 -1
- package/starter-packs/operations/Plugins/operations-starter/access.md +1 -1
- package/starter-packs/sales/Plugins/sales-starter/access.md +1 -1
- package/starter-packs/support/Plugins/support-starter/access.md +1 -1
- package/agent-guide/finding-things.md +0 -7
- package/agent-guide/introduction.md +0 -27
- package/dist/modules/workspace/bounded-matcher.d.ts +0 -73
- package/dist/modules/workspace/bounded-matcher.d.ts.map +0 -1
- package/dist/modules/workspace/bounded-matcher.js +0 -200
- package/dist/modules/workspace/bounded-matcher.js.map +0 -1
- package/src/modules/workspace/__tests__/bounded-matcher.test.ts +0 -213
- package/src/modules/workspace/bounded-matcher.ts +0 -271
|
@@ -3,9 +3,15 @@
|
|
|
3
3
|
* services (roles + groups) drive for "which access rules name this
|
|
4
4
|
* principal" and "rewrite every reference atomically":
|
|
5
5
|
*
|
|
6
|
-
* - `
|
|
7
|
-
* parse
|
|
8
|
-
*
|
|
6
|
+
* - `resolverRoleRefs`: what IS a principal reference — the resolver's own
|
|
7
|
+
* parse of each file, so the reports name every enforced rule.
|
|
8
|
+
* - `findRoleRefsInText` / `rewriteRoleTokensInText`: the line-level walk
|
|
9
|
+
* the rename edits text with. It handles block lists and `key: value`
|
|
10
|
+
* entries; quoted and flow-YAML own frontmatter is rewritten by the
|
|
11
|
+
* full parser's own tree (`rewriteOwnFrontmatterStructurally`). The
|
|
12
|
+
* rewrite re-checks its result with `resolverRoleRefs` and refuses
|
|
13
|
+
* when a reference survives, so the warning and the rename can never
|
|
14
|
+
* disagree.
|
|
9
15
|
* - `KbReferenceScanner`: candidate collection (every KB file with an
|
|
10
16
|
* access-frontmatter extension — `.md` and `.tool`, the resolver's own
|
|
11
17
|
* set), the advisory reference scan (cached — roster mutations must not
|
|
@@ -14,24 +20,37 @@
|
|
|
14
20
|
* file's ORIGINAL text too, so rollback snapshots reuse the read the
|
|
15
21
|
* rewrite already did instead of a second full-KB pass).
|
|
16
22
|
*
|
|
17
|
-
* Tokens here are the ENTRY-GRAMMAR canonical tokens: a bare name (
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
23
|
+
* Tokens here are the ENTRY-GRAMMAR canonical tokens: a bare name (the role
|
|
24
|
+
* when one exists, else the group) or an explicit `role/<name>` /
|
|
25
|
+
* `group/<name>`. Callers translate: a ROLE's references are its bare token
|
|
26
|
+
* plus its `role/` token; a GROUP's are its `group/` token, plus the bare
|
|
27
|
+
* token while no role shares the name.
|
|
21
28
|
*/
|
|
22
29
|
|
|
23
30
|
import path from 'node:path';
|
|
24
31
|
import { promises as fs } from 'node:fs';
|
|
25
32
|
|
|
26
|
-
import {
|
|
33
|
+
import { Scalar, YAMLSeq, isAlias, isMap, isNode, isScalar, isSeq, parseDocument, type Document } from 'yaml';
|
|
34
|
+
import { isFrontmatterFence, type IWorkspaceService } from '@bevel-software/platform-shared';
|
|
35
|
+
import type { ITreeWalker } from '../../shared/fs.contract.js';
|
|
36
|
+
import { logger } from '../../shared/logging.js';
|
|
37
|
+
import { printable, sanitizedPath } from '../../shared/printable.js';
|
|
27
38
|
import {
|
|
39
|
+
AccessDeniedError,
|
|
28
40
|
KNOWN_VERBS,
|
|
29
41
|
accessMdDeclaresBodyRules,
|
|
42
|
+
accessMdSelfEntries,
|
|
43
|
+
canonicalRoleName,
|
|
30
44
|
hasAccessFrontmatterExtension,
|
|
31
45
|
isAccessMdPath,
|
|
32
46
|
parseAccessEntry,
|
|
47
|
+
parseAccessFile,
|
|
48
|
+
parseOwnAccessEntries,
|
|
33
49
|
stripComment,
|
|
34
50
|
} from '../access-model/index.js';
|
|
51
|
+
import type { ParsedEntry, Verb } from '../access-model/index.js';
|
|
52
|
+
|
|
53
|
+
const log = logger('access');
|
|
35
54
|
|
|
36
55
|
/**
|
|
37
56
|
* Resolve the [start, end) line range that role rewrites may touch — the YAML
|
|
@@ -237,8 +256,8 @@ function walkRoleRefs(
|
|
|
237
256
|
|
|
238
257
|
/**
|
|
239
258
|
* Every genuine role/group reference in `text`'s rule regions, as
|
|
240
|
-
* {role, verb} where `role` is the canonical ENTRY TOKEN (bare name
|
|
241
|
-
* `role/<name>`). `isAccessMd` marks an `access.md` file, whose BODY is a
|
|
259
|
+
* {role, verb} where `role` is the canonical ENTRY TOKEN (bare name,
|
|
260
|
+
* `role/<name>` or `group/<name>`). `isAccessMd` marks an `access.md` file, whose BODY is a
|
|
242
261
|
* rule source in the body-governed format — see {@link ruleLineRanges}.
|
|
243
262
|
*/
|
|
244
263
|
export function findRoleRefsInText(
|
|
@@ -291,6 +310,115 @@ export function rewriteRoleTokensInText(
|
|
|
291
310
|
return changed ? lines.join('\n') : text;
|
|
292
311
|
}
|
|
293
312
|
|
|
313
|
+
/**
|
|
314
|
+
* The structural rewrite, for the references the line walk cannot edit:
|
|
315
|
+
* a file's OWN frontmatter is read by the full YAML parser whenever it holds
|
|
316
|
+
* flow sequences, quoted scalars or other real YAML (`ownEntriesRoot` in the
|
|
317
|
+
* grammar), and the line walk only knows block lists and `key: value`. So
|
|
318
|
+
* this edits the same document the resolver reads — the frontmatter's
|
|
319
|
+
* top-level verb keys, by the full parser's own node tree with the same
|
|
320
|
+
* FAILSAFE schema — and replaces each scalar entry whose token is in
|
|
321
|
+
* `oldTokens`, keeping its quoting, its comments and every other node. An
|
|
322
|
+
* alias the resolver reads as a verb's entries is never edited through: one
|
|
323
|
+
* that names an old token is replaced by a plain scalar, and one that is the
|
|
324
|
+
* verb's whole list (`read: *team`, `&team` a list) by a rewritten copy of
|
|
325
|
+
* that list, so the anchor it points at (which may sit under any other key)
|
|
326
|
+
* is left alone.
|
|
327
|
+
*
|
|
328
|
+
* The other rule sources — a legacy `access.md`'s frontmatter and a
|
|
329
|
+
* body-governed one's body — are read by the YAML-subset parser only, which
|
|
330
|
+
* the line walk mirrors, so they never need this.
|
|
331
|
+
*/
|
|
332
|
+
function rewriteOwnFrontmatterStructurally(
|
|
333
|
+
text: string,
|
|
334
|
+
oldTokens: ReadonlySet<string>,
|
|
335
|
+
newDisplayName: string,
|
|
336
|
+
): string {
|
|
337
|
+
const lines = text.split('\n');
|
|
338
|
+
const { start, end } = configLineRange(lines, true);
|
|
339
|
+
if (start >= end) return text;
|
|
340
|
+
// Typed as an editable document: the rewrite puts new, unparsed scalars in it.
|
|
341
|
+
const doc: Document = parseDocument(lines.slice(start, end).join('\n'), { schema: 'failsafe' });
|
|
342
|
+
// A document the full parser rejects is not what the resolver read: it fell
|
|
343
|
+
// back to the subset parser, whose reading the line walk already covers.
|
|
344
|
+
if (doc.errors.length > 0 || !isMap(doc.contents)) return text;
|
|
345
|
+
let changed = false;
|
|
346
|
+
const rewritten = (node: unknown): Scalar | null => {
|
|
347
|
+
const target = isAlias(node) ? node.resolve(doc) : node;
|
|
348
|
+
if (!isScalar(target) || typeof target.value !== 'string') return null;
|
|
349
|
+
const parsed = parseAccessEntry(target.value);
|
|
350
|
+
if (!parsed.ok || parsed.entry.kind !== 'role' || !oldTokens.has(parsed.entry.role)) return null;
|
|
351
|
+
const value = `${parsed.entry.deny ? 'deny ' : ''}${newDisplayName}`;
|
|
352
|
+
changed = true;
|
|
353
|
+
if (isAlias(node)) return new Scalar(value);
|
|
354
|
+
target.value = value;
|
|
355
|
+
return target;
|
|
356
|
+
};
|
|
357
|
+
// The anchored list an alias names, as a rewritten copy — or null when
|
|
358
|
+
// none of its entries changes, so the alias stays. Each item is cloned
|
|
359
|
+
// before it is rewritten (the anchor's own items stay as they are) and
|
|
360
|
+
// loses any anchor of its own (YAML allows one definition of a name).
|
|
361
|
+
const rewrittenCopy = (seq: YAMLSeq): YAMLSeq | null => {
|
|
362
|
+
let hit = false;
|
|
363
|
+
const copy = new YAMLSeq(doc.schema);
|
|
364
|
+
copy.flow = seq.flow;
|
|
365
|
+
copy.items = seq.items.map((item) => {
|
|
366
|
+
const clone = isNode(item) ? item.clone() : item;
|
|
367
|
+
if (isScalar(clone)) clone.anchor = undefined;
|
|
368
|
+
const r = rewritten(clone);
|
|
369
|
+
if (r) hit = true;
|
|
370
|
+
return r ?? clone;
|
|
371
|
+
});
|
|
372
|
+
return hit ? copy : null;
|
|
373
|
+
};
|
|
374
|
+
for (const pair of doc.contents.items) {
|
|
375
|
+
const key = isScalar(pair.key) ? pair.key.value : pair.key;
|
|
376
|
+
if (typeof key !== 'string' || !VERB_KEYS.has(key)) continue;
|
|
377
|
+
const aliased = isAlias(pair.value) ? pair.value.resolve(doc) : null;
|
|
378
|
+
if (isSeq(aliased)) {
|
|
379
|
+
const copy = rewrittenCopy(aliased);
|
|
380
|
+
if (copy) pair.value = copy;
|
|
381
|
+
} else if (isSeq(pair.value)) {
|
|
382
|
+
pair.value.items = pair.value.items.map((item) => rewritten(item) ?? item);
|
|
383
|
+
} else {
|
|
384
|
+
const scalar = rewritten(pair.value);
|
|
385
|
+
if (scalar) pair.value = scalar;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
if (!changed) return text;
|
|
389
|
+
// Never fold a long value onto a second line, nor pad a flow list.
|
|
390
|
+
const frontmatter = doc.toString({ lineWidth: 0, flowCollectionPadding: false }).replace(/\n$/, '');
|
|
391
|
+
return [...lines.slice(0, start), frontmatter, ...lines.slice(end)].join('\n');
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* The principal references the RESOLVER reads from one candidate file, by
|
|
396
|
+
* the resolver's own parsers: an `access.md`'s folder rules
|
|
397
|
+
* (`parseAccessFile`) and its self-rules (`accessMdSelfEntries`), any other
|
|
398
|
+
* file's own frontmatter (`parseOwnAccessEntries`). Those parsers read
|
|
399
|
+
* quoted scalars and flow sequences (`read: ["Admin"]`) the line walker
|
|
400
|
+
* above does not, so the reports built on this name every rule that is
|
|
401
|
+
* actually enforced — and nothing the resolver ignores (a broken
|
|
402
|
+
* `access.md` is dropped from the model, so it references nothing).
|
|
403
|
+
*/
|
|
404
|
+
export function resolverRoleRefs(text: string, repoRel: string): { role: string; verb: string }[] {
|
|
405
|
+
const out: { role: string; verb: string }[] = [];
|
|
406
|
+
const add = (entries: Record<Verb, ParsedEntry[]> | null): void => {
|
|
407
|
+
if (!entries) return;
|
|
408
|
+
for (const verb of KNOWN_VERBS) {
|
|
409
|
+
for (const e of entries[verb]) if (e.kind === 'role') out.push({ role: e.role, verb });
|
|
410
|
+
}
|
|
411
|
+
};
|
|
412
|
+
if (isAccessMdPath(repoRel)) {
|
|
413
|
+
const folder = parseAccessFile(text, repoRel);
|
|
414
|
+
if (folder.ok) add(folder.file.entries);
|
|
415
|
+
add(accessMdSelfEntries(text));
|
|
416
|
+
} else {
|
|
417
|
+
add(parseOwnAccessEntries(text));
|
|
418
|
+
}
|
|
419
|
+
return out;
|
|
420
|
+
}
|
|
421
|
+
|
|
294
422
|
export interface ReferenceHit {
|
|
295
423
|
path: string;
|
|
296
424
|
verb: string;
|
|
@@ -304,6 +432,75 @@ export interface ReferenceRewrite {
|
|
|
304
432
|
original: string;
|
|
305
433
|
}
|
|
306
434
|
|
|
435
|
+
/**
|
|
436
|
+
* Which of the given repo-relative paths the person a refusal is for may
|
|
437
|
+
* read — `canReadBatch` bound to that person. A path is itself a disclosure,
|
|
438
|
+
* so a refusal names only these and says nothing of the rest.
|
|
439
|
+
*/
|
|
440
|
+
export type PathVisibility = (repoRelPaths: string[]) => Promise<ReadonlyMap<string, boolean>>;
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* The one refusal for a rename blocked by a place the renaming person may
|
|
444
|
+
* not be shown — a folder the walk cannot list, a file it cannot read, or a
|
|
445
|
+
* file the person may not read that still names the old principal after the
|
|
446
|
+
* rewrite (a backstop: every form the resolver reads is rewritten). The same
|
|
447
|
+
* words whatever the cause: no path, no count, no hint of a reference, since
|
|
448
|
+
* Admin status does not grant read and each of those is a disclosure. The
|
|
449
|
+
* log names the place.
|
|
450
|
+
*/
|
|
451
|
+
export const UNSHOWN_REFERENCE_REFUSAL =
|
|
452
|
+
'This rename rewrites access rules across the knowledge base, and part of the knowledge base could not be rewritten, so it may still reference the old principal; rename aborted with no changes. The server log names where.';
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* The commit's own write gate refusing a rename's rewrite, translated for the
|
|
456
|
+
* person who asked. The gate judges every file the rewrite touches against the
|
|
457
|
+
* actor's WRITE rules (a rename's references reach files private to others,
|
|
458
|
+
* which an Admin may be unable to write), and its refusal names the file. A
|
|
459
|
+
* file its person may read keeps that refusal: the path and its eligible
|
|
460
|
+
* writers say whom to ask. A file they may not read gets
|
|
461
|
+
* `UNSHOWN_REFERENCE_REFUSAL` instead, so the hidden path stays unnamed; the
|
|
462
|
+
* log names it. Anything else comes back unchanged.
|
|
463
|
+
*
|
|
464
|
+
* The one bit left (some file the person cannot see names the old principal)
|
|
465
|
+
* is the hidden-blocker exception of "A Guard's Answer Is a Disclosure": an
|
|
466
|
+
* Admin's refused rename of a principal they manage says only that an unshown
|
|
467
|
+
* place blocks it.
|
|
468
|
+
*/
|
|
469
|
+
export async function translateRewriteWriteRefusal(
|
|
470
|
+
err: unknown,
|
|
471
|
+
visible: PathVisibility,
|
|
472
|
+
makeError: (message: string) => Error,
|
|
473
|
+
): Promise<unknown> {
|
|
474
|
+
if (!(err instanceof AccessDeniedError)) return err;
|
|
475
|
+
const p = err.access.path;
|
|
476
|
+
let shown = false;
|
|
477
|
+
try {
|
|
478
|
+
shown = (await visible([p])).get(p) === true;
|
|
479
|
+
} catch {
|
|
480
|
+
// Cannot tell whether the person may see it: say nothing of it.
|
|
481
|
+
}
|
|
482
|
+
if (shown) return err;
|
|
483
|
+
log.warn(`reference rewrite: the acting person may not write ${printable(p)}`);
|
|
484
|
+
const translated = makeError(UNSHOWN_REFERENCE_REFUSAL);
|
|
485
|
+
// withFileLocks reads the paths whose restore failed off the error it
|
|
486
|
+
// catches, to discard their partial bytes; the translation must carry them.
|
|
487
|
+
const unrestored = (err as { unrestoredPaths?: Set<string> }).unrestoredPaths;
|
|
488
|
+
if (unrestored) (translated as { unrestoredPaths?: Set<string> }).unrestoredPaths = unrestored;
|
|
489
|
+
return translated;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* The rows of `refs` whose file `readable` allows. The rest are dropped
|
|
494
|
+
* outright, not counted: Admin status does not grant read, so even a count of
|
|
495
|
+
* rules in content the viewer cannot read is a disclosure.
|
|
496
|
+
*/
|
|
497
|
+
export function readableReferences(
|
|
498
|
+
refs: readonly ReferenceHit[],
|
|
499
|
+
readable: ReadonlyMap<string, boolean>,
|
|
500
|
+
): ReferenceHit[] {
|
|
501
|
+
return refs.filter((r) => readable.get(r.path) === true);
|
|
502
|
+
}
|
|
503
|
+
|
|
307
504
|
/** How long a cached reference scan may serve after its load. Backstop only —
|
|
308
505
|
* reference-changing writes invalidate explicitly (the admin services after
|
|
309
506
|
* their own rewrites, and the event-bus tap below for everyone else's). */
|
|
@@ -354,6 +551,8 @@ export class KbReferenceScanner {
|
|
|
354
551
|
|
|
355
552
|
constructor(
|
|
356
553
|
private readonly workspaceService: IWorkspaceService,
|
|
554
|
+
/** The resolver's walk (see `collectCandidateFiles`), injected by the composition root. */
|
|
555
|
+
private readonly disk: ITreeWalker,
|
|
357
556
|
private readonly kbDirName: string,
|
|
358
557
|
eventBus?: ReferenceScanInvalidationBus,
|
|
359
558
|
) {
|
|
@@ -381,37 +580,40 @@ export class KbReferenceScanner {
|
|
|
381
580
|
* resolver reads access verbs from — the shared
|
|
382
581
|
* `ACCESS_FRONTMATTER_EXTENSIONS` set (`.md` AND `.tool`; a rename that
|
|
383
582
|
* skipped `.tool` would strand a live frontmatter grant there). Sourced
|
|
384
|
-
* from the
|
|
385
|
-
* `.bevelignore`)
|
|
386
|
-
*
|
|
387
|
-
*
|
|
388
|
-
*
|
|
389
|
-
*
|
|
583
|
+
* from the RESOLVER'S OWN walk (`walkKb`: dot-entries and vendored
|
|
584
|
+
* dependencies skipped, `.bevelignore` NOT honoured) — the resolver
|
|
585
|
+
* enforces an `access.md` the explorer hides, so a scan that honoured the
|
|
586
|
+
* ignore file would leave its rules out of the roster's reports and a
|
|
587
|
+
* rename would strand them. Paths are relative to the KB dir. (`roles.yaml`
|
|
588
|
+
* matches no scanned extension, so it's excluded; callers commit it
|
|
589
|
+
* separately.)
|
|
590
|
+
*
|
|
591
|
+
* `holes` names every folder the walk could not list: the candidate set
|
|
592
|
+
* is INCOMPLETE when it is non-empty, and each caller decides what that
|
|
593
|
+
* means — the rewrite refuses, the advisory scan says so and does not cache.
|
|
390
594
|
*/
|
|
391
|
-
async collectCandidateFiles(workspaceId: string): Promise<string[]> {
|
|
392
|
-
const tree = await this.workspaceService.listFiles(workspaceId);
|
|
393
|
-
const prefix = `${this.kbDirName}/`;
|
|
595
|
+
async collectCandidateFiles(workspaceId: string): Promise<{ files: string[]; holes: string[] }> {
|
|
394
596
|
const out: string[] = [];
|
|
395
|
-
const
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
};
|
|
404
|
-
visit(tree);
|
|
405
|
-
return out;
|
|
597
|
+
const { holes } = await this.disk.walkKb(await this.repoDir(workspaceId), [
|
|
598
|
+
{
|
|
599
|
+
onFile(dir, name) {
|
|
600
|
+
const rel = dir ? `${dir}/${name}` : name;
|
|
601
|
+
if (hasAccessFrontmatterExtension(rel)) out.push(rel);
|
|
602
|
+
},
|
|
603
|
+
},
|
|
604
|
+
]);
|
|
605
|
+
return { files: out, holes };
|
|
406
606
|
}
|
|
407
607
|
|
|
408
608
|
/**
|
|
409
609
|
* Sound scan of EVERY candidate file (folder `access.md` + `.md`/`.tool`
|
|
410
610
|
* node frontmatter) for genuine principal references, indexed by canonical
|
|
411
|
-
* entry TOKEN
|
|
412
|
-
*
|
|
413
|
-
*
|
|
414
|
-
*
|
|
611
|
+
* entry TOKEN, read by the resolver's own parse ({@link resolverRoleRefs}).
|
|
612
|
+
* Shares the candidate set with the rename rewrite, and the rewrite checks
|
|
613
|
+
* its result against the same parse, so the delete warning and the rewrite
|
|
614
|
+
* cannot disagree on what references a principal. A file we cannot
|
|
615
|
+
* read is skipped (this is an advisory read, not the atomic write path),
|
|
616
|
+
* and the scan that skipped it is not cached.
|
|
415
617
|
* Cached per workspace (see class doc); reads are batched with Promise.all
|
|
416
618
|
* rather than serial per-file awaits.
|
|
417
619
|
*/
|
|
@@ -428,7 +630,12 @@ export class KbReferenceScanner {
|
|
|
428
630
|
const startedUnder = flight.gen;
|
|
429
631
|
try {
|
|
430
632
|
const repoDir = await this.repoDir(workspaceId);
|
|
431
|
-
const candidates = await this.collectCandidateFiles(workspaceId);
|
|
633
|
+
const { files: candidates, holes } = await this.collectCandidateFiles(workspaceId);
|
|
634
|
+
if (holes.length > 0) {
|
|
635
|
+
log.warn(
|
|
636
|
+
`reference scan: cannot list ${holes.map((h) => printable(h || '.')).join(', ')} — references there are not reported`,
|
|
637
|
+
);
|
|
638
|
+
}
|
|
432
639
|
const texts = await Promise.all(
|
|
433
640
|
candidates.map(async (repoRel) => {
|
|
434
641
|
try {
|
|
@@ -442,7 +649,7 @@ export class KbReferenceScanner {
|
|
|
442
649
|
candidates.forEach((repoRel, i) => {
|
|
443
650
|
const text = texts[i];
|
|
444
651
|
if (text === null) return;
|
|
445
|
-
for (const ref of
|
|
652
|
+
for (const ref of resolverRoleRefs(text, repoRel)) {
|
|
446
653
|
const list = byToken.get(ref.role);
|
|
447
654
|
if (list) list.push({ path: repoRel, verb: ref.verb });
|
|
448
655
|
else byToken.set(ref.role, [{ path: repoRel, verb: ref.verb }]);
|
|
@@ -451,7 +658,10 @@ export class KbReferenceScanner {
|
|
|
451
658
|
// Cache only when no invalidation landed mid-scan; the caller still gets
|
|
452
659
|
// this snapshot (at worst one write stale — same as the pre-scan world),
|
|
453
660
|
// but a stale snapshot must never STICK for a TTL. The next call re-scans.
|
|
454
|
-
|
|
661
|
+
// An incomplete scan (a folder could not be listed, or a candidate file
|
|
662
|
+
// could not be read) is never cached either: the next call retries it.
|
|
663
|
+
const unread = texts.some((t) => t === null);
|
|
664
|
+
if (flight.gen === startedUnder && holes.length === 0 && !unread) {
|
|
455
665
|
this.cache.set(workspaceId, { loadedAt: Date.now(), byToken });
|
|
456
666
|
}
|
|
457
667
|
return byToken;
|
|
@@ -462,22 +672,50 @@ export class KbReferenceScanner {
|
|
|
462
672
|
}
|
|
463
673
|
|
|
464
674
|
/**
|
|
465
|
-
* Find every genuine reference to `oldToken`
|
|
466
|
-
* `newDisplayName`. FAIL-CLOSED: a candidate we cannot read might reference
|
|
675
|
+
* Find every genuine reference to `oldToken` (or any of several spellings
|
|
676
|
+
* of one principal) and rewrite it to `newDisplayName`. FAIL-CLOSED: a candidate we cannot read might reference
|
|
467
677
|
* the old token, and skipping it would commit a partial rewrite (a
|
|
468
678
|
* half-renamed principal still pointed at by stragglers = silent access
|
|
469
679
|
* drop) — so any read failure aborts via `makeError` with nothing written.
|
|
470
680
|
* Returns the writes WITH each file's original text (rollback snapshots).
|
|
681
|
+
*
|
|
682
|
+
* The rename keeps the KB's references to a principal whole, so it rewrites
|
|
683
|
+
* every candidate, files its person may not read included: a file private
|
|
684
|
+
* to others blocks nothing here, and no answer of this method depends on
|
|
685
|
+
* what such a file holds. Whether the person may WRITE each rewritten file
|
|
686
|
+
* is the commit's gate to decide, not this method's (see
|
|
687
|
+
* `translateRewriteWriteRefusal`). Every form the resolver reads
|
|
688
|
+
* is rewritten — block lists and `key: value` by the line walk, quoted and
|
|
689
|
+
* flow-YAML own frontmatter by the structural rewrite — so there is no
|
|
690
|
+
* "cannot rewrite" answer for hidden content to give. The writes reach a
|
|
691
|
+
* caller only through surfaces gated on read (the roster, the event
|
|
692
|
+
* delivery), never through this method's answer.
|
|
693
|
+
*
|
|
694
|
+
* A reference the resolver still sees after both rewrites is refused: named
|
|
695
|
+
* with how to fix it when `visible` says its person may read the file, with
|
|
696
|
+
* `UNSHOWN_REFERENCE_REFUSAL` otherwise (no path, no count). Nothing the
|
|
697
|
+
* resolver reads should reach that, so the refusal is a backstop, not an
|
|
698
|
+
* answer the content of a hidden file can choose.
|
|
471
699
|
*/
|
|
472
700
|
async rewriteReferences(
|
|
473
701
|
workspaceId: string,
|
|
474
|
-
oldToken: string,
|
|
702
|
+
oldToken: string | readonly string[],
|
|
475
703
|
newDisplayName: string,
|
|
476
704
|
makeError: (message: string, cause?: string) => Error,
|
|
705
|
+
visible: PathVisibility,
|
|
477
706
|
): Promise<ReferenceRewrite[]> {
|
|
707
|
+
const oldTokens = typeof oldToken === 'string' ? [oldToken] : oldToken;
|
|
478
708
|
const repoDir = await this.repoDir(workspaceId);
|
|
479
709
|
const writes: ReferenceRewrite[] = [];
|
|
480
|
-
const candidates = await this.collectCandidateFiles(workspaceId);
|
|
710
|
+
const { files: candidates, holes } = await this.collectCandidateFiles(workspaceId);
|
|
711
|
+
// A folder we cannot list might hold references to the old token — same
|
|
712
|
+
// fail-closed rule as an unreadable file below, and never named nor
|
|
713
|
+
// counted, for the same reason: its own access.md may be just as
|
|
714
|
+
// unreadable, so `visible` could not see the rules that close it.
|
|
715
|
+
if (holes.length > 0) {
|
|
716
|
+
log.warn(`reference rewrite: cannot list ${holes.map((h) => printable(h || '.')).join(', ')}`);
|
|
717
|
+
throw makeError(UNSHOWN_REFERENCE_REFUSAL);
|
|
718
|
+
}
|
|
481
719
|
const texts = await Promise.all(
|
|
482
720
|
candidates.map(async (repoRel) => {
|
|
483
721
|
try {
|
|
@@ -489,20 +727,55 @@ export class KbReferenceScanner {
|
|
|
489
727
|
);
|
|
490
728
|
for (const t of texts) {
|
|
491
729
|
if (!t.ok) {
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
730
|
+
// Never named, not even to a reader: `visible` decides from the
|
|
731
|
+
// file's own rules, and a file we cannot read has none it can see —
|
|
732
|
+
// an inherited grant would then disclose a path its own rules may
|
|
733
|
+
// close. The fs cause carries the path too, so it stays in the log.
|
|
734
|
+
log.warn(`reference rewrite: cannot read ${printable(t.repoRel)}`, { cause: t.cause });
|
|
735
|
+
throw makeError(UNSHOWN_REFERENCE_REFUSAL);
|
|
496
736
|
}
|
|
497
737
|
}
|
|
738
|
+
// Old tokens the rename must leave no reference to (the new name's own
|
|
739
|
+
// token is exempt: it IS the rewrite's result).
|
|
740
|
+
const newToken = canonicalRoleName(newDisplayName);
|
|
741
|
+
const gone = new Set(oldTokens.filter((t) => t !== newToken));
|
|
742
|
+
const stranded: string[] = [];
|
|
498
743
|
candidates.forEach((repoRel, i) => {
|
|
499
744
|
const entry = texts[i];
|
|
500
745
|
if (!entry.ok) return; // unreachable — the loop above threw
|
|
501
|
-
|
|
746
|
+
let rewritten = entry.text;
|
|
747
|
+
for (const token of oldTokens) {
|
|
748
|
+
rewritten = rewriteRoleTokensInText(rewritten, token, newDisplayName, true, isAccessMdPath(repoRel));
|
|
749
|
+
}
|
|
750
|
+
// The line rewrite edits block-list and `key: value` entries; the
|
|
751
|
+
// resolver also reads quoted and flow-YAML own frontmatter, which the
|
|
752
|
+
// structural rewrite edits by the full parser's own tree.
|
|
753
|
+
const stillNamed = (t: string) => resolverRoleRefs(t, repoRel).some((r) => gone.has(r.role));
|
|
754
|
+
if (stillNamed(rewritten)) {
|
|
755
|
+
rewritten = rewriteOwnFrontmatterStructurally(rewritten, new Set(oldTokens), newDisplayName);
|
|
756
|
+
}
|
|
757
|
+
// A reference the resolver still sees would be stranded by the rename.
|
|
758
|
+
if (stillNamed(rewritten)) {
|
|
759
|
+
stranded.push(repoRel);
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
502
762
|
if (rewritten !== entry.text) {
|
|
503
763
|
writes.push({ repoRelativePath: repoRel, content: rewritten, original: entry.text });
|
|
504
764
|
}
|
|
505
765
|
});
|
|
766
|
+
if (stranded.length > 0) {
|
|
767
|
+
const verdicts = await visible(stranded);
|
|
768
|
+
const shown = stranded.filter((p) => verdicts.get(p) === true);
|
|
769
|
+
if (shown.length < stranded.length) {
|
|
770
|
+
log.warn(
|
|
771
|
+
`reference rewrite: cannot rewrite ${stranded.map((p) => printable(p || '.')).join(', ')}`,
|
|
772
|
+
);
|
|
773
|
+
throw makeError(UNSHOWN_REFERENCE_REFUSAL);
|
|
774
|
+
}
|
|
775
|
+
throw makeError(
|
|
776
|
+
`${shown.map((p) => sanitizedPath(p || '.')).join(', ')} name the old principal in a form the rename cannot rewrite; rewrite it as a \`- Name\` list item, then rename again. Rename aborted with no changes`,
|
|
777
|
+
);
|
|
778
|
+
}
|
|
506
779
|
return writes;
|
|
507
780
|
}
|
|
508
781
|
|
|
@@ -39,15 +39,22 @@ const log = logger('roles.recover');
|
|
|
39
39
|
import type { WorkflowEventBus } from '../workflow/index.js';
|
|
40
40
|
import type { AuthUser, IWorkspaceService, IWorkflowService } from '@bevel-software/platform-shared';
|
|
41
41
|
import { WorkflowDomainError } from '../../shared/domain-errors.js';
|
|
42
|
+
import type { ITreeWalker } from '../../shared/fs.contract.js';
|
|
42
43
|
import type { IAccessControl } from './access-control.interface.js';
|
|
43
44
|
import {
|
|
44
45
|
ADMIN_CANONICAL,
|
|
45
46
|
ROLE_TOKEN_PREFIX,
|
|
46
47
|
canonicalRoleName,
|
|
47
48
|
canonicalEmail,
|
|
49
|
+
makeRolesYamlWriteValidator,
|
|
50
|
+
renderRolesYaml,
|
|
51
|
+
GROUPS_YAML,
|
|
52
|
+
SYNCED_GROUPS_YAML,
|
|
53
|
+
validateGroupsFile,
|
|
54
|
+
GROUP_REF_PREFIX,
|
|
55
|
+
RESERVED_ROLE_NAMES,
|
|
48
56
|
} from '../access-model/index.js';
|
|
49
57
|
import { loadActiveGroups } from './access-control.service.js';
|
|
50
|
-
import { makeRolesYamlWriteValidator, renderRolesYaml, GROUPS_YAML, SYNCED_GROUPS_YAML, validateGroupsFile, GROUP_REF_PREFIX, RESERVED_ROLE_NAMES } from '../access-model/index.js';
|
|
51
58
|
import {
|
|
52
59
|
parseRolesModel,
|
|
53
60
|
deleteRole as editDeleteRole,
|
|
@@ -67,7 +74,7 @@ import {
|
|
|
67
74
|
} from './groups-edit.js';
|
|
68
75
|
import { capabilityRoleFor, isLegacyPeopleSetRole } from './capability-registry.js';
|
|
69
76
|
import { AdminLockedCommits } from './admin-locked-commit.js';
|
|
70
|
-
import { KbReferenceScanner } from './reference-scan.js';
|
|
77
|
+
import { KbReferenceScanner, readableReferences } from './reference-scan.js';
|
|
71
78
|
|
|
72
79
|
/** A mutation that cannot proceed (invariant violation, contention, not found). */
|
|
73
80
|
export class RolesAdminError extends WorkflowDomainError {
|
|
@@ -118,11 +125,11 @@ export interface RoleRosterEntry {
|
|
|
118
125
|
capability: { description: string; groupAssignable: boolean } | null;
|
|
119
126
|
/**
|
|
120
127
|
* Every access rule referencing this ROLE — folder `access.md` AND node
|
|
121
|
-
* frontmatter. `role/<name>` hits
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
128
|
+
* frontmatter. `role/<name>` and BARE-token hits both count — a bare name
|
|
129
|
+
* is always the role when the role exists, even when a group shares the
|
|
130
|
+
* name (that group is `group/<name>`). Sound: this is the SAME scan the
|
|
131
|
+
* group rename/delete rewrites use. Only files the roster's viewer can read
|
|
132
|
+
* are listed (see `getRoster`).
|
|
126
133
|
*/
|
|
127
134
|
referencedBy: { path: string; verb: string }[];
|
|
128
135
|
}
|
|
@@ -180,6 +187,8 @@ export class RolesAdminService {
|
|
|
180
187
|
private readonly workspaceService: IWorkspaceService,
|
|
181
188
|
private readonly workflowService: IWorkflowService,
|
|
182
189
|
private readonly accessControl: IAccessControl,
|
|
190
|
+
/** The composition root's walk — the one the resolver finds access rules with. */
|
|
191
|
+
disk: ITreeWalker,
|
|
183
192
|
private readonly kbDirName: string,
|
|
184
193
|
/**
|
|
185
194
|
* The branch whose roles.yaml is authoritative for admin status, read
|
|
@@ -223,7 +232,7 @@ export class RolesAdminService {
|
|
|
223
232
|
// The event bus keeps the roster's `referencedBy` fresh: a share-dialog
|
|
224
233
|
// grant/revoke on any access.md happens outside this service, and the
|
|
225
234
|
// scanner invalidates its cache on those writes' events (TTL as backstop).
|
|
226
|
-
this.references = new KbReferenceScanner(workspaceService, kbDirName, eventBus);
|
|
235
|
+
this.references = new KbReferenceScanner(workspaceService, disk, kbDirName, eventBus);
|
|
227
236
|
this.fixedAdminEmails = [
|
|
228
237
|
...new Set(deploymentAdmins.filter(Boolean).map((e) => canonicalEmail(e)).filter(Boolean)),
|
|
229
238
|
];
|
|
@@ -303,23 +312,24 @@ export class RolesAdminService {
|
|
|
303
312
|
|
|
304
313
|
// ---- Read -------------------------------------------------------------
|
|
305
314
|
|
|
306
|
-
/**
|
|
307
|
-
|
|
315
|
+
/**
|
|
316
|
+
* The roster: every role on the default branch with members + references.
|
|
317
|
+
* `viewerEmail` is who the roster is for: `referencedBy` names only the
|
|
318
|
+
* files that viewer can read and omits the rest entirely, without a count —
|
|
319
|
+
* being an Admin is not read access to every file, and a path, or even a
|
|
320
|
+
* count of rules in unreadable content, is itself a disclosure.
|
|
321
|
+
*/
|
|
322
|
+
async getRoster(viewerEmail: string): Promise<RoleRosterEntry[]> {
|
|
308
323
|
const workspaceId = await this.ensureWorkspace();
|
|
309
324
|
const text = await this.readRolesYaml(workspaceId);
|
|
310
325
|
const model = parseRolesModel(text);
|
|
311
326
|
// ONE sound scan of every candidate file (folder access.md + node
|
|
312
327
|
// frontmatter) — cached in the shared scanner, so mutations returning the
|
|
313
328
|
// fresh roster don't rerun the full-KB sweep — indexed by canonical entry
|
|
314
|
-
// token. Attribution per spelling: a `role/<name>` hit
|
|
315
|
-
// role's
|
|
316
|
-
//
|
|
317
|
-
|
|
318
|
-
const [referencesByToken, activeGroups] = await Promise.all([
|
|
319
|
-
this.references.scan(workspaceId),
|
|
320
|
-
loadActiveGroups((f) => this.locked.readKbFile(workspaceId, f)),
|
|
321
|
-
]);
|
|
322
|
-
const groupOwnedBareNames = new Set(activeGroups.groups.keys());
|
|
329
|
+
// token. Attribution per spelling: a `role/<name>` hit and a BARE hit are
|
|
330
|
+
// both the role's — bare names resolve role-first, so a same-named group
|
|
331
|
+
// is only ever `group/<name>` (the groups roster's mirror image).
|
|
332
|
+
const referencesByToken = await this.references.scan(workspaceId);
|
|
323
333
|
const out: RoleRosterEntry[] = [];
|
|
324
334
|
for (const role of model) {
|
|
325
335
|
const canonical = canonicalRoleName(role.displayName);
|
|
@@ -342,11 +352,18 @@ export class RolesAdminService {
|
|
|
342
352
|
? { description: registryEntry.description, groupAssignable: registryEntry.groupAssignable }
|
|
343
353
|
: null,
|
|
344
354
|
referencedBy: [
|
|
345
|
-
...(
|
|
355
|
+
...(referencesByToken.get(canonical) ?? []),
|
|
346
356
|
...(referencesByToken.get(`${ROLE_TOKEN_PREFIX}${canonical}`) ?? []),
|
|
347
357
|
],
|
|
348
358
|
});
|
|
349
359
|
}
|
|
360
|
+
const paths = [...new Set(out.flatMap((r) => r.referencedBy.map((ref) => ref.path)))];
|
|
361
|
+
if (paths.length > 0) {
|
|
362
|
+
const readable = await this.accessControl.canReadBatch(workspaceId, viewerEmail, paths);
|
|
363
|
+
for (const r of out) {
|
|
364
|
+
r.referencedBy = readableReferences(r.referencedBy, readable);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
350
367
|
return out;
|
|
351
368
|
}
|
|
352
369
|
|
|
@@ -450,7 +467,7 @@ export class RolesAdminService {
|
|
|
450
467
|
);
|
|
451
468
|
this.accessControl.invalidate(workspaceId);
|
|
452
469
|
this.emitWrites(workspaceId, actor, [OLD_ROLES_YAML, ROLES_YAML]);
|
|
453
|
-
return this.getRoster();
|
|
470
|
+
return this.getRoster(actor.email);
|
|
454
471
|
}
|
|
455
472
|
|
|
456
473
|
// ---- Mutations --------------------------------------------------------
|
|
@@ -484,7 +501,7 @@ export class RolesAdminService {
|
|
|
484
501
|
): Promise<RoleRosterEntry[]> {
|
|
485
502
|
this.assertNotFixedAdmin(canonical, email);
|
|
486
503
|
await this.runEdit(actor, (text) => editAddMember(text, canonical, email), opts?.aroundWrite);
|
|
487
|
-
return this.getRoster();
|
|
504
|
+
return this.getRoster(actor.email);
|
|
488
505
|
}
|
|
489
506
|
|
|
490
507
|
/**
|
|
@@ -542,7 +559,7 @@ export class RolesAdminService {
|
|
|
542
559
|
}
|
|
543
560
|
return editRemoveMember(text, canonical, email);
|
|
544
561
|
});
|
|
545
|
-
return this.getRoster();
|
|
562
|
+
return this.getRoster(actor.email);
|
|
546
563
|
}
|
|
547
564
|
|
|
548
565
|
/**
|
|
@@ -572,12 +589,12 @@ export class RolesAdminService {
|
|
|
572
589
|
}
|
|
573
590
|
await this.runEdit(actor, (text) => editAddGroupRef(text, canonical, groupName));
|
|
574
591
|
});
|
|
575
|
-
return this.getRoster();
|
|
592
|
+
return this.getRoster(actor.email);
|
|
576
593
|
}
|
|
577
594
|
|
|
578
595
|
async unassignGroup(actor: AuthUser, canonical: string, groupName: string): Promise<RoleRosterEntry[]> {
|
|
579
596
|
await this.runEdit(actor, (text) => editRemoveGroupRef(text, canonical, groupName));
|
|
580
|
-
return this.getRoster();
|
|
597
|
+
return this.getRoster(actor.email);
|
|
581
598
|
}
|
|
582
599
|
|
|
583
600
|
/**
|
|
@@ -689,7 +706,7 @@ export class RolesAdminService {
|
|
|
689
706
|
);
|
|
690
707
|
this.accessControl.invalidate(workspaceId);
|
|
691
708
|
this.emitWrites(workspaceId, actor, [ROLES_YAML, GROUPS_YAML]);
|
|
692
|
-
return this.getRoster();
|
|
709
|
+
return this.getRoster(actor.email);
|
|
693
710
|
}
|
|
694
711
|
|
|
695
712
|
// ---- Internals --------------------------------------------------------
|