@bevel-software/platform-core-backend 0.33.0 → 0.34.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 +19 -8
- package/agent-guide/html-views.md +231 -5
- package/agent-guide/tool-manuals.md +1 -1
- 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 +14 -7
- package/dist/core/create-core-server.js.map +1 -1
- package/dist/core/create-core-services.d.ts +16 -5
- package/dist/core/create-core-services.d.ts.map +1 -1
- package/dist/core/create-core-services.js +37 -12
- 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 +17 -9
- package/dist/modules/access/access-control.interface.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.d.ts +12 -2
- package/dist/modules/access/access-control.service.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.js +164 -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 +35 -29
- 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/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.tools.d.ts.map +1 -1
- package/dist/modules/agent-guide/agent-guide.tools.js +13 -3
- package/dist/modules/agent-guide/agent-guide.tools.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 +3 -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 +16 -1
- 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/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 +68 -13
- 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/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 +15 -3
- package/dist/modules/secrets-vault/secrets-vault.routes.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 +24 -12
- 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-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/tool-manuals.contract.d.ts +19 -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 +13 -6
- package/dist/modules/tool-manuals/tool-manuals.service.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.service.js +23 -15
- package/dist/modules/tool-manuals/tool-manuals.service.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.tools.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.tools.js +25 -10
- 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 +14 -9
- 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/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.map +1 -1
- package/dist/modules/workspace/workspace.tools.js +460 -116
- package/dist/modules/workspace/workspace.tools.js.map +1 -1
- 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/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 +3 -3
- package/src/__tests__/pg-scratch.test.ts +69 -0
- package/src/__tests__/pg-scratch.ts +31 -0
- package/src/__tests__/slow-ripgrep.ts +65 -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/catalog-revision.ts +24 -2
- package/src/core/core-ports.ts +42 -0
- package/src/core/create-core-server.ts +13 -0
- package/src/core/create-core-services.ts +49 -12
- 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 +2 -0
- 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__/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 +17 -10
- package/src/modules/access/access-control.service.ts +158 -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 +43 -33
- 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/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 +31 -0
- package/src/modules/agent-guide/agent-guide.tools.ts +13 -2
- package/src/modules/agent-instructions/__tests__/shared-file-rules.test.ts +1 -0
- package/src/modules/agent-instructions/compose.ts +3 -1
- package/src/modules/agent-instructions/shared-file-rules.ts +17 -1
- 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/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 +107 -17
- 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/secrets-vault.routes.ts +23 -3
- 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 +44 -13
- 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__/can-write.tools.test.ts +3 -1
- package/src/modules/tool-helpers/tool-context.ts +10 -0
- 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.service.test.ts +42 -0
- package/src/modules/tool-manuals/__tests__/tool-manuals.tools.test.ts +3 -3
- package/src/modules/tool-manuals/index.ts +6 -2
- package/src/modules/tool-manuals/tool-manuals.contract.ts +18 -6
- package/src/modules/tool-manuals/tool-manuals.routes.ts +40 -3
- package/src/modules/tool-manuals/tool-manuals.service.ts +31 -15
- package/src/modules/tool-manuals/tool-manuals.tools.ts +28 -11
- package/src/modules/tool-registry/__tests__/tool-description-length.test.ts +2 -2
- 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 +14 -9
- 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 +2 -2
- package/src/modules/workspace/__tests__/workspace.tools.branch-errors.test.ts +22 -10
- package/src/modules/workspace/__tests__/workspace.tools.test.ts +393 -113
- 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 +500 -155
- package/src/shared/path-order.ts +37 -8
- 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/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
|
@@ -72,7 +72,7 @@ export interface SyncedGroupsSource {
|
|
|
72
72
|
|
|
73
73
|
// Name safety is the SHARED predicate in `group-files.ts` (`unsafeNameReason`)
|
|
74
74
|
// — the same rules the manual group editor asserts, including the reserved
|
|
75
|
-
// `role/`
|
|
75
|
+
// `role/` and `group/` prefixes. Here the name arrives from the IdP, so an unsafe group is
|
|
76
76
|
// SKIPPED with a warning instead of erroring (fail-closed: an unrepresentable
|
|
77
77
|
// group grants nothing).
|
|
78
78
|
|
|
@@ -6,8 +6,44 @@ import {
|
|
|
6
6
|
accessFrontmatterExtensionList,
|
|
7
7
|
parseOwnAccessEntries,
|
|
8
8
|
parseRolesYaml,
|
|
9
|
+
parseAccessEntry,
|
|
10
|
+
mergeGroupsIntoRoles,
|
|
9
11
|
} from '../access-grammar.js';
|
|
10
|
-
import { parseGroupsFile } from '../group-files.js';
|
|
12
|
+
import { parseGroupsFile, unsafeNameReason } from '../group-files.js';
|
|
13
|
+
|
|
14
|
+
describe('principal spellings — a bare name is the role, group/<Name> the group', () => {
|
|
15
|
+
it('parses group/<Name> into the canonical group token, spacing normalised', () => {
|
|
16
|
+
const r = parseAccessEntry('group/ Product Team');
|
|
17
|
+
expect(r).toEqual({ ok: true, entry: { kind: 'role', role: 'group/product team', displayRole: 'group/ Product Team', deny: false } });
|
|
18
|
+
const d = parseAccessEntry('deny group/Admin');
|
|
19
|
+
expect(d.ok && d.entry.kind === 'role' && d.entry.role).toBe('group/admin');
|
|
20
|
+
expect(parseAccessEntry('group/').ok).toBe(false);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('reserves the group/ prefix for roles and groups alike', () => {
|
|
24
|
+
const roles = parseRolesYaml('roles:\n Admin:\n - a@x.io\n group/Ops:\n - b@x.io\n');
|
|
25
|
+
expect(roles.ok).toBe(false);
|
|
26
|
+
if (!roles.ok) expect(roles.errors.join(' ')).toMatch(/reserved 'group\/' prefix/);
|
|
27
|
+
expect(unsafeNameReason('group/Ops')).toMatch(/group\//);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('merging keeps the bare key for the role and registers group/ for every group', () => {
|
|
31
|
+
const parsed = parseRolesYaml('roles:\n Admin:\n - admin@x.io\n');
|
|
32
|
+
if (!parsed.ok) throw new Error('roles');
|
|
33
|
+
const groups = parseGroupsFile('groups:\n Admin:\n - mallory@x.io\n Sales:\n - sam@x.io\n', 'groups.yaml');
|
|
34
|
+
if (!groups.ok) throw new Error('groups');
|
|
35
|
+
const warnings = mergeGroupsIntoRoles(parsed.index, groups.groups, 'groups.yaml');
|
|
36
|
+
const idx = parsed.index.byCanonical;
|
|
37
|
+
expect(idx.get('admin')?.kind).toBe('role');
|
|
38
|
+
expect(idx.get('role/admin')).toBe(idx.get('admin'));
|
|
39
|
+
expect(idx.get('group/admin')?.kind).toBe('group');
|
|
40
|
+
expect(idx.get('sales')?.kind).toBe('group');
|
|
41
|
+
expect(idx.get('group/sales')).toBe(idx.get('sales'));
|
|
42
|
+
// The group's member holds only the group's keys, never the role's.
|
|
43
|
+
expect([...(parsed.index.byEmail.get('mallory@x.io') ?? [])]).toEqual(['group/admin']);
|
|
44
|
+
expect(warnings.join(' ')).toMatch(/resolves to the ROLE; use 'group\/admin'/);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
11
47
|
|
|
12
48
|
describe('parseYamlSubset — inline empty collections', () => {
|
|
13
49
|
it('reads `key: []` as an empty list, not the scalar "[]"', () => {
|
|
@@ -103,6 +139,7 @@ describe('the access-frontmatter extension set', () => {
|
|
|
103
139
|
expect(accessFrontmatterExtensionList()).toHaveLength(after);
|
|
104
140
|
expect(accessFrontmatterExtensionList()).toContain('.md');
|
|
105
141
|
expect(accessFrontmatterExtensionList()).toContain('.tool');
|
|
142
|
+
expect(accessFrontmatterExtensionList()).not.toContain('.agent');
|
|
106
143
|
});
|
|
107
144
|
|
|
108
145
|
it('throws on a malformed extension rather than skipping it', () => {
|
|
@@ -302,12 +302,13 @@ describe('round-trip — every granted entry parses back', () => {
|
|
|
302
302
|
});
|
|
303
303
|
});
|
|
304
304
|
|
|
305
|
-
describe('token-kind family — exact-token grants,
|
|
306
|
-
// One name,
|
|
307
|
-
//
|
|
308
|
-
//
|
|
309
|
-
// revokes are alias-tolerant
|
|
310
|
-
//
|
|
305
|
+
describe('token-kind family — exact-token grants, kind-aware revokes', () => {
|
|
306
|
+
// One name, three spellings, two possible principals: `role/` is always the
|
|
307
|
+
// ROLE's, `group/` always the GROUP's, and the bare token is the role's when
|
|
308
|
+
// one exists, else the group's. Grants must be exact-token idempotent;
|
|
309
|
+
// revokes are alias-tolerant (explicit + bare, never the other kind's
|
|
310
|
+
// explicit spelling) only when the bare name is the principal's — the
|
|
311
|
+
// caller passes tokenMatch: 'exact' otherwise.
|
|
311
312
|
const SHARED = `---
|
|
312
313
|
write:
|
|
313
314
|
- role/Sales
|
|
@@ -360,10 +361,51 @@ write:
|
|
|
360
361
|
expect(lines).not.toContain(' - Sales');
|
|
361
362
|
});
|
|
362
363
|
|
|
363
|
-
it("REVOKE
|
|
364
|
+
it("REVOKE bare principal: 'name' matching strips both role spellings", () => {
|
|
364
365
|
const r = spliceRevoke(SHARED, 'write', { kind: 'role', role: 'Sales' });
|
|
365
366
|
expect(r.changed).toBe(true);
|
|
366
|
-
expect(r.text).not.toContain('Sales'); // bare token
|
|
367
|
+
expect(r.text).not.toContain('Sales'); // the bare token is the role — same principal
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
const ALL = `---
|
|
371
|
+
write:
|
|
372
|
+
- role/Sales
|
|
373
|
+
- Sales
|
|
374
|
+
- group/Sales
|
|
375
|
+
- Admin
|
|
376
|
+
---
|
|
377
|
+
`;
|
|
378
|
+
|
|
379
|
+
it('GRANT is exact-token: a group/<Name> grant goes through beside the bare and role/ spellings', () => {
|
|
380
|
+
const md = '---\nwrite:\n - role/Sales\n - Sales\n---\n';
|
|
381
|
+
const r = spliceGrant(md, 'write', { kind: 'role', role: 'group/Sales' });
|
|
382
|
+
expect(r.changed).toBe(true);
|
|
383
|
+
expect(r.text.split('\n')).toContain(' - group/Sales');
|
|
384
|
+
expect(spliceGrant(ALL, 'write', { kind: 'role', role: 'group/Sales' }).changed).toBe(false);
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
it("REVOKE 'name' never crosses kinds: the role's revoke keeps group/, the group's keeps role/", () => {
|
|
388
|
+
const asRole = spliceRevoke(ALL, 'write', { kind: 'role', role: 'role/Sales' });
|
|
389
|
+
expect(asRole.text.split('\n')).toEqual(['---', 'write:', ' - group/Sales', ' - Admin', '---', '']);
|
|
390
|
+
|
|
391
|
+
// The service asks 'name' for a GROUP only while no role shares the name
|
|
392
|
+
// (with one, as in ALL, it picks 'exact' — the last test below), so the
|
|
393
|
+
// group's case is a file where the bare token can only be the group.
|
|
394
|
+
const GROUP_ONLY = '---\nwrite:\n - Sales\n - group/Sales\n - Admin\n---\n';
|
|
395
|
+
const asGroup = spliceRevoke(GROUP_ONLY, 'write', { kind: 'role', role: 'group/Sales' });
|
|
396
|
+
expect(asGroup.text.split('\n')).toEqual(['---', 'write:', ' - Admin', '---', '']);
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
it('REVOKE matches a spaced `group/ <Name>` request against the parsed `group/<name>` entry', () => {
|
|
400
|
+
const r = spliceRevoke(ALL, 'write', { kind: 'role', role: 'group/ Sales' }, { tokenMatch: 'exact' });
|
|
401
|
+
expect(r.text.split('\n')).toEqual(['---', 'write:', ' - role/Sales', ' - Sales', ' - Admin', '---', '']);
|
|
402
|
+
const md = '---\nwrite:\n - group/ Sales\n---\n';
|
|
403
|
+
expect(spliceRevoke(md, 'write', { kind: 'role', role: 'group/Sales' }).changed).toBe(true);
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
it("REVOKE 'exact' of group/<Name> strips only that spelling", () => {
|
|
407
|
+
const r = spliceRevoke(ALL, 'write', { kind: 'role', role: 'group/Sales' }, { tokenMatch: 'exact' });
|
|
408
|
+
expect(r.text.split('\n')).toEqual(['---', 'write:', ' - role/Sales', ' - Sales', ' - Admin', '---', '']);
|
|
367
409
|
});
|
|
368
410
|
});
|
|
369
411
|
|
|
@@ -92,14 +92,47 @@ export const DENY_PREFIX = 'deny ';
|
|
|
92
92
|
export const GROUP_REF_PREFIX = 'group:';
|
|
93
93
|
/**
|
|
94
94
|
* Explicit ROLE token prefix in access.md entries: `role/<Name>` resolves to
|
|
95
|
-
* the roles.yaml role only, never a group. A BARE name resolves
|
|
96
|
-
* and falls back to the
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
95
|
+
* the roles.yaml role only, never a group. A BARE name resolves ROLE-FIRST
|
|
96
|
+
* and falls back to the group, so a group that shares a role's name is
|
|
97
|
+
* reached only as `group/<Name>` (see `GROUP_TOKEN_PREFIX`) — a group named
|
|
98
|
+
* `Admin` never takes over the grants written for the Admin role. The prefix
|
|
99
|
+
* is reserved in the group name-safety rules (a group may never be named
|
|
100
|
+
* `role/...`), and every role is also registered in the principal index under
|
|
101
|
+
* its `role/<canonical>` alias.
|
|
100
102
|
*/
|
|
101
103
|
export const ROLE_TOKEN_PREFIX = 'role/';
|
|
102
104
|
|
|
105
|
+
/**
|
|
106
|
+
* Explicit GROUP token prefix in access.md entries: `group/<Name>` resolves
|
|
107
|
+
* to the active source's group only, never a role. Every group is registered
|
|
108
|
+
* in the principal index under its `group/<canonical>` alias; the bare name
|
|
109
|
+
* reaches the group only while no role of that name exists. The share dialog
|
|
110
|
+
* and the access API always write groups this way. Reserved in the group
|
|
111
|
+
* name-safety rules and in roles.yaml like `role/`.
|
|
112
|
+
*/
|
|
113
|
+
export const GROUP_TOKEN_PREFIX = 'group/';
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* A canonical role-shaped token split into its explicit kind prefix and its
|
|
117
|
+
* bare name: `role/admin` → `{ prefix: 'role', name: 'admin' }`, `group/admin`
|
|
118
|
+
* → `{ prefix: 'group', name: 'admin' }`, anything else (a bare name, a
|
|
119
|
+
* plugin token, `everyone`) → `{ prefix: null, name: <token> }`. A prefixed
|
|
120
|
+
* name is canonicalised again, so `group/ admin` splits like `group/admin`
|
|
121
|
+
* (as `parseAccessEntry` reads it).
|
|
122
|
+
*/
|
|
123
|
+
export function splitPrincipalToken(canonical: string): {
|
|
124
|
+
prefix: 'role' | 'group' | null;
|
|
125
|
+
name: string;
|
|
126
|
+
} {
|
|
127
|
+
if (canonical.startsWith(ROLE_TOKEN_PREFIX)) {
|
|
128
|
+
return { prefix: 'role', name: canonicalRoleName(canonical.slice(ROLE_TOKEN_PREFIX.length)) };
|
|
129
|
+
}
|
|
130
|
+
if (canonical.startsWith(GROUP_TOKEN_PREFIX)) {
|
|
131
|
+
return { prefix: 'group', name: canonicalRoleName(canonical.slice(GROUP_TOKEN_PREFIX.length)) };
|
|
132
|
+
}
|
|
133
|
+
return { prefix: null, name: canonical };
|
|
134
|
+
}
|
|
135
|
+
|
|
103
136
|
/**
|
|
104
137
|
* PLUGIN-principal token prefix in access.md entries: `plugin/<Name>/<verb>`
|
|
105
138
|
* names everyone who holds `<verb>` on the plugin folder `Plugins/<Name>` —
|
|
@@ -585,6 +618,11 @@ export function parseAccessEntry(
|
|
|
585
618
|
const suffix = canonicalRoleName(role.slice(ROLE_TOKEN_PREFIX.length));
|
|
586
619
|
if (!suffix) return { ok: false, error: `entry '${body}' names no role after '${ROLE_TOKEN_PREFIX}'` };
|
|
587
620
|
role = `${ROLE_TOKEN_PREFIX}${suffix}`;
|
|
621
|
+
} else if (role.startsWith(GROUP_TOKEN_PREFIX)) {
|
|
622
|
+
// Explicit group token, normalised the same way to `group/<canonicalName>`.
|
|
623
|
+
const suffix = canonicalRoleName(role.slice(GROUP_TOKEN_PREFIX.length));
|
|
624
|
+
if (!suffix) return { ok: false, error: `entry '${body}' names no group after '${GROUP_TOKEN_PREFIX}'` };
|
|
625
|
+
role = `${GROUP_TOKEN_PREFIX}${suffix}`;
|
|
588
626
|
} else if (role.startsWith(PLUGIN_TOKEN_PREFIX)) {
|
|
589
627
|
// `plugin/<Name>/<verb>`: the name is canonicalised to the plugin's
|
|
590
628
|
// manifest slug, the verb must be one of the three. Anything else is a
|
|
@@ -649,6 +687,12 @@ export function parseRolesYaml(
|
|
|
649
687
|
);
|
|
650
688
|
continue;
|
|
651
689
|
}
|
|
690
|
+
if (canonical.startsWith(GROUP_TOKEN_PREFIX)) {
|
|
691
|
+
errors.push(
|
|
692
|
+
`roles.yaml: role '${displayName}' starts with the reserved '${GROUP_TOKEN_PREFIX}' prefix — that spelling is the explicit group token in access entries`,
|
|
693
|
+
);
|
|
694
|
+
continue;
|
|
695
|
+
}
|
|
652
696
|
// A role spelled `plugin/…` would be overwritten by the synthesised plugin
|
|
653
697
|
// principal of the same key while its members kept the token — plugin
|
|
654
698
|
// access for people who are not plugin members. Refused at parse time,
|
|
@@ -730,15 +774,17 @@ export function parseRolesYaml(
|
|
|
730
774
|
*
|
|
731
775
|
* Rules (the grant-grammar precedence):
|
|
732
776
|
* - Every role is ALSO registered under its explicit `role/<canonical>`
|
|
733
|
-
* alias
|
|
734
|
-
*
|
|
735
|
-
*
|
|
736
|
-
*
|
|
777
|
+
* alias, and every group under its explicit `group/<canonical>` alias —
|
|
778
|
+
* the tokens that always resolve to that kind.
|
|
779
|
+
* - A BARE name resolves ROLE-FIRST: when a group's canonical name
|
|
780
|
+
* collides with a role's, the bare key stays the ROLE's (warned); the
|
|
781
|
+
* group is reachable only via `group/<canonical>`. So a group named
|
|
782
|
+
* `Admin` — whoever proposed it — never gains what `Admin` is granted.
|
|
737
783
|
* - Role `group:<Name>` refs — Admin's included — expand against the
|
|
738
784
|
* merged groups; an unknown ref contributes nothing (warned).
|
|
739
785
|
* - `byEmail` is rebuilt so each member holds exactly the tokens that
|
|
740
786
|
* resolve to a principal they belong to (bare + `role/` alias for roles,
|
|
741
|
-
* bare for groups).
|
|
787
|
+
* `group/` alias plus the bare name when the group owns it for groups).
|
|
742
788
|
*/
|
|
743
789
|
export function mergeGroupsIntoRoles(
|
|
744
790
|
index: RolesIndex,
|
|
@@ -765,19 +811,18 @@ export function mergeGroupsIntoRoles(
|
|
|
765
811
|
}
|
|
766
812
|
}
|
|
767
813
|
|
|
768
|
-
// 2. Bare-name precedence:
|
|
769
|
-
// `
|
|
814
|
+
// 2. Bare-name precedence: roles keep the bare token; every group gets its
|
|
815
|
+
// `group/<canonical>` alias, and the bare key only when no role owns it.
|
|
770
816
|
for (const [canonical, def] of groups) {
|
|
817
|
+
const group = { displayName: def.displayName, emails: new Set(def.emails), kind: 'group' as const };
|
|
818
|
+
index.byCanonical.set(`${GROUP_TOKEN_PREFIX}${canonical}`, group);
|
|
771
819
|
if (roleRecords.has(canonical)) {
|
|
772
820
|
warnings.push(
|
|
773
|
-
`${sourceFile}: group '${def.displayName}' shares its name with a role — the bare name
|
|
821
|
+
`${sourceFile}: group '${def.displayName}' shares its name with a role — the bare name resolves to the ROLE; use '${GROUP_TOKEN_PREFIX}${canonical}' to reference the group`,
|
|
774
822
|
);
|
|
823
|
+
continue;
|
|
775
824
|
}
|
|
776
|
-
index.byCanonical.set(canonical,
|
|
777
|
-
displayName: def.displayName,
|
|
778
|
-
emails: new Set(def.emails),
|
|
779
|
-
kind: 'group',
|
|
780
|
-
});
|
|
825
|
+
index.byCanonical.set(canonical, group);
|
|
781
826
|
}
|
|
782
827
|
|
|
783
828
|
// 3. Explicit `role/<canonical>` alias for every role (same record — the
|
|
@@ -788,8 +833,8 @@ export function mergeGroupsIntoRoles(
|
|
|
788
833
|
}
|
|
789
834
|
|
|
790
835
|
// 4. Rebuild the email → tokens map from the final index so membership
|
|
791
|
-
// reflects the post-precedence keys (a collided
|
|
792
|
-
//
|
|
836
|
+
// reflects the post-precedence keys (a collided group's members hold
|
|
837
|
+
// only its `group/` alias, never the role's bare token).
|
|
793
838
|
index.byEmail.clear();
|
|
794
839
|
for (const [key, principal] of index.byCanonical) {
|
|
795
840
|
for (const email of principal.emails) {
|
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
canonicalEmail,
|
|
34
34
|
canonicalRoleName,
|
|
35
35
|
accessMdDeclaresBodyRules,
|
|
36
|
+
splitPrincipalToken,
|
|
36
37
|
} from './access-grammar.js';
|
|
37
38
|
import { freshAccessMd } from './access-template.js';
|
|
38
39
|
import { scanFrontmatter } from './frontmatter-lines.js';
|
|
@@ -149,15 +150,18 @@ export function validatePrincipal(p: Principal): Principal {
|
|
|
149
150
|
/**
|
|
150
151
|
* How a role-shaped principal's TOKEN is compared against a file entry:
|
|
151
152
|
*
|
|
152
|
-
* - `'exact'`: the entry must carry the SAME spelling — a bare token
|
|
153
|
-
* `role/<name>` token
|
|
154
|
-
* bare
|
|
155
|
-
* always use this: granting
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
* - `'name'`:
|
|
159
|
-
*
|
|
160
|
-
*
|
|
153
|
+
* - `'exact'`: the entry must carry the SAME spelling — a bare token, a
|
|
154
|
+
* `role/<name>` token and a `group/<name>` token can be DIFFERENT
|
|
155
|
+
* principals (bare = the role when one exists, else the group). Grants
|
|
156
|
+
* always use this: granting `group/X` while `role/X` or a bare `X` is
|
|
157
|
+
* present must NOT be treated as already-granted, or the group silently
|
|
158
|
+
* never receives the grant.
|
|
159
|
+
* - `'name'`: the principal's explicit spelling AND the bare name match —
|
|
160
|
+
* `role/X` (or a bare `X`) matches `X` and `role/X`; `group/X` matches
|
|
161
|
+
* `X` and `group/X`. Never across kinds: a role's revoke never touches
|
|
162
|
+
* `group/X`, a group's never `role/X`. Revokes use this only when the
|
|
163
|
+
* bare name is this principal's (AccessMutationService decides), so
|
|
164
|
+
* revoking also removes the legacy bare spelling.
|
|
161
165
|
*/
|
|
162
166
|
export type TokenMatch = 'exact' | 'name';
|
|
163
167
|
|
|
@@ -169,11 +173,14 @@ export type TokenMatch = 'exact' | 'name';
|
|
|
169
173
|
function entryMatches(entry: ParsedEntry, p: Principal, tokenMatch: TokenMatch): boolean {
|
|
170
174
|
if (p.kind === 'user') return entry.kind === 'user' && entry.email === canonicalEmail(p.email);
|
|
171
175
|
if (entry.kind !== 'role') return false;
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
|
|
176
|
+
// Compare split, canonical halves: `group/ Product` in the request names the
|
|
177
|
+
// same principal as the `group/product` the parser reads from the file.
|
|
178
|
+
const own = splitPrincipalToken(canonicalRoleName(p.role));
|
|
179
|
+
const theirs = splitPrincipalToken(entry.role);
|
|
180
|
+
if (theirs.name !== own.name) return false;
|
|
181
|
+
if (tokenMatch === 'exact') return theirs.prefix === own.prefix;
|
|
182
|
+
// Same family only: a bare principal stands for the role, as `role/` does.
|
|
183
|
+
return theirs.prefix === null || theirs.prefix === (own.prefix ?? 'role');
|
|
177
184
|
}
|
|
178
185
|
|
|
179
186
|
// ---------------------------------------------------------------------------
|
|
@@ -344,10 +351,10 @@ function blockEntries(fm: string[], block: VerbBlock): { entry: ParsedEntry; lin
|
|
|
344
351
|
* Add `principal` under `verb`. Idempotent: if an entry with the EXACT same
|
|
345
352
|
* token spelling already exists with the SAME deny-ness (a grant when
|
|
346
353
|
* granting, a deny when denying), returns `changed: false`. Idempotency is
|
|
347
|
-
* deliberately exact-token, never name-level: a bare token
|
|
348
|
-
* token
|
|
349
|
-
*
|
|
350
|
-
* `role/X` is present, and vice versa. Creates the verb key (and the
|
|
354
|
+
* deliberately exact-token, never name-level: a bare token, a `role/` token
|
|
355
|
+
* and a `group/` token can be different principals (the bare key is the
|
|
356
|
+
* role's when one exists), so a `group/X` grant must go through even when a
|
|
357
|
+
* bare `X` or `role/X` is present, and vice versa. Creates the verb key (and the
|
|
351
358
|
* frontmatter fence, for a fresh file) as needed. Preserves everything else
|
|
352
359
|
* byte-for-byte.
|
|
353
360
|
*
|
|
@@ -445,11 +452,12 @@ export function spliceGrant(
|
|
|
445
452
|
* Preserves everything else.
|
|
446
453
|
*
|
|
447
454
|
* Token matching for role-shaped principals is `tokenMatch` (default `'name'`:
|
|
448
|
-
*
|
|
449
|
-
* Callers pass `'exact'` when
|
|
450
|
-
*
|
|
451
|
-
* revoke of
|
|
452
|
-
*
|
|
455
|
+
* the explicit spelling and the bare one — revoking removes legacy bare
|
|
456
|
+
* spellings too). Callers pass `'exact'` when the bare name belongs to the
|
|
457
|
+
* OTHER kind (a group revoke while a same-named role owns the bare key, or a
|
|
458
|
+
* role revoke while only a group of that name exists), so a revoke of one
|
|
459
|
+
* never strips the other (see AccessMutationService, which derives the flag
|
|
460
|
+
* from the merged principal index).
|
|
453
461
|
*/
|
|
454
462
|
export function spliceRevoke(
|
|
455
463
|
text: string,
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
RESERVED_ROLE_NAMES,
|
|
5
5
|
PLUGIN_TOKEN_PREFIX,
|
|
6
6
|
ROLE_TOKEN_PREFIX,
|
|
7
|
+
GROUP_TOKEN_PREFIX,
|
|
7
8
|
canonicalEmail,
|
|
8
9
|
canonicalRoleName,
|
|
9
10
|
parseYamlSubset,
|
|
@@ -58,6 +59,7 @@ export const SYNCED_GROUPS_YAML = 'synced-groups.yaml';
|
|
|
58
59
|
* \x00-\x1f control chars / newlines break line structure outright
|
|
59
60
|
* `role/…` reserved: that spelling is the EXPLICIT role token in access
|
|
60
61
|
* entries — a group carrying it could never be referenced.
|
|
62
|
+
* `group/…` reserved likewise: the EXPLICIT group token.
|
|
61
63
|
*/
|
|
62
64
|
export function unsafeNameReason(displayName: string): string | null {
|
|
63
65
|
const trimmed = displayName.trim();
|
|
@@ -69,6 +71,9 @@ export function unsafeNameReason(displayName: string): string | null {
|
|
|
69
71
|
if (canonicalRoleName(trimmed).startsWith(ROLE_TOKEN_PREFIX)) {
|
|
70
72
|
return `starts with the reserved '${ROLE_TOKEN_PREFIX}' prefix (the explicit role token in access entries)`;
|
|
71
73
|
}
|
|
74
|
+
if (canonicalRoleName(trimmed).startsWith(GROUP_TOKEN_PREFIX)) {
|
|
75
|
+
return `starts with the reserved '${GROUP_TOKEN_PREFIX}' prefix (the explicit group token in access entries)`;
|
|
76
|
+
}
|
|
72
77
|
if (canonicalRoleName(trimmed).startsWith(PLUGIN_TOKEN_PREFIX)) {
|
|
73
78
|
return `starts with the reserved '${PLUGIN_TOKEN_PREFIX}' prefix (the plugin-principal token in access entries)`;
|
|
74
79
|
}
|
|
@@ -367,6 +367,7 @@ describe('the HTML views section', () => {
|
|
|
367
367
|
expect(text).toContain('**No host navigation.**');
|
|
368
368
|
expect(text).toContain('`window.bevel.openNode(href)`');
|
|
369
369
|
expect(text).toContain('`window.bevel.navigate(href)`');
|
|
370
|
+
expect(text).toContain('`window.bevel.branch` — the branch the page is viewed on');
|
|
370
371
|
expect(text).toContain('A distribution may add data members of its own to `window.bevel`');
|
|
371
372
|
expect(text).toContain('**A bare fragment scrolls the page.**');
|
|
372
373
|
// Rendered for the deployment's layout like every other section.
|
|
@@ -374,6 +375,36 @@ describe('the HTML views section', () => {
|
|
|
374
375
|
expect(docs).toContain('/workspace/main/Docs/Alice.md');
|
|
375
376
|
});
|
|
376
377
|
|
|
378
|
+
// A dashboard that painted one "Loading…" line and then swapped in the whole
|
|
379
|
+
// board read as broken on a cold load (2026-10-20). These rules are what
|
|
380
|
+
// agents write pages from; the reference page under them is run by
|
|
381
|
+
// core-frontend's `liveDataReferencePage.test.ts`.
|
|
382
|
+
it('tells page authors how a page loads live data without changing shape', async () => {
|
|
383
|
+
const text = section(await composeAgentGuide(DEFAULT_KB_LAYOUT), '## HTML views').replace(/\s+/g, ' ');
|
|
384
|
+
expect(text).toContain('### Live data: a page that stays still while it loads');
|
|
385
|
+
expect(text).toContain('**Render the final page shell in the markup, before any data.**');
|
|
386
|
+
expect(text).toContain('The loading treatment — placeholder rows, `aria-busy="true"` — sits **inside** the data regions.');
|
|
387
|
+
expect(text).toContain('**Publish one consistent dataset at a time.**');
|
|
388
|
+
expect(text).toContain('Until then show no counts, no cards and no empty columns');
|
|
389
|
+
expect(text).toContain('**Refresh keeps the current board.**');
|
|
390
|
+
expect(text).toContain('**A failed read fails the whole load, visibly.**');
|
|
391
|
+
expect(text).toContain('offer a Retry button that runs the same load again');
|
|
392
|
+
expect(text).toContain('**Read in bounded parallel once the paths are known.**');
|
|
393
|
+
expect(text).toContain('**Load with `callTool`, not `callToolChain`.**');
|
|
394
|
+
expect(text).toContain("**The reads are the viewer's own.**");
|
|
395
|
+
expect(text).toContain('**Link to what the tools named by the path they answered.**');
|
|
396
|
+
expect(text).toContain('Never put the checkout folder in front of a path a tool answered: it is already there.');
|
|
397
|
+
expect(text).toContain('plain HTML and one inline script, no build step and no framework');
|
|
398
|
+
// The rule names the deployment's own checkout folder; the page links on its branch by the tool's path.
|
|
399
|
+
const docs = section(await composeAgentGuide(DEFAULT_KB_LAYOUT, undefined, { kbDirName: 'kb' }), '## HTML views');
|
|
400
|
+
expect(docs.replace(/\s+/g, ' ')).toContain('starts with the checkout folder, `kb/Data/Tickets/A.md`');
|
|
401
|
+
// Each segment of the tool's path encoded on its own, so a name carrying `#` stays a name.
|
|
402
|
+
expect(docs).toContain(
|
|
403
|
+
"link.href = '/workspace/' + encodeURIComponent(BRANCH) + '/' + ticket.path.split('/').map(encodeURIComponent).join('/');",
|
|
404
|
+
);
|
|
405
|
+
expect(docs).not.toContain('{{');
|
|
406
|
+
});
|
|
407
|
+
|
|
377
408
|
it('documents window.bevel.tools: its four functions, the null check, the consent rule and requireTools with its card', async () => {
|
|
378
409
|
const text = section(await composeAgentGuide(DEFAULT_KB_LAYOUT), '## HTML views').replace(/\s+/g, ' ');
|
|
379
410
|
for (const member of [
|
|
@@ -102,10 +102,21 @@ export function registerAgentGuideTool(
|
|
|
102
102
|
router.post(
|
|
103
103
|
`/agent/tools/${GET_AGENT_GUIDE_TOOL}`,
|
|
104
104
|
toolAuth,
|
|
105
|
-
toolHandler(async (args) => {
|
|
105
|
+
toolHandler(async (args, ctx) => {
|
|
106
106
|
const list = await sections();
|
|
107
107
|
const wanted = typeof args.section === 'string' ? args.section.trim() : '';
|
|
108
|
-
|
|
108
|
+
// A connection made as an agent configuration is told so at the top of the
|
|
109
|
+
// whole guide: the catalog it describes is narrowed to that agent.
|
|
110
|
+
// Everything is told nothing new, so its guide is the same text.
|
|
111
|
+
const connection = ctx.connectionScope?.describe() ?? null;
|
|
112
|
+
if (!wanted) {
|
|
113
|
+
const guide = joinGuideSections(list);
|
|
114
|
+
return {
|
|
115
|
+
guide: connection ? `> ${connection}\n\n${guide}` : guide,
|
|
116
|
+
sections: list.map((s) => ({ id: s.id, title: s.title })),
|
|
117
|
+
...(connection ? { connection } : {}),
|
|
118
|
+
};
|
|
119
|
+
}
|
|
109
120
|
const one = list.find((s) => s.id === wanted);
|
|
110
121
|
if (!one) {
|
|
111
122
|
throw new ToolError(
|
|
@@ -90,8 +90,10 @@ export function platformInstructions(layout: KbLayout): string {
|
|
|
90
90
|
* own cap, and the preamble under its cap plus the marker a cut appends. The
|
|
91
91
|
* test is what makes it a ceiling rather than a hope: a shared rule that grew
|
|
92
92
|
* past it fails before it reaches an agent.
|
|
93
|
+
*
|
|
94
|
+
* 14,400 since the shared rules' own cap rose by 400 (the `__tmp__/` ref).
|
|
93
95
|
*/
|
|
94
|
-
export const INSTRUCTIONS_CAP =
|
|
96
|
+
export const INSTRUCTIONS_CAP = 14_400;
|
|
95
97
|
|
|
96
98
|
/** The one-line marker that replaces everything past the preamble cap. */
|
|
97
99
|
export const PREAMBLE_TRUNCATION_MARKER = `[preamble truncated at ${PREAMBLE_CAP.toLocaleString('en-US')} characters; shorten ${PREAMBLE_FILE}]`;
|
|
@@ -61,8 +61,12 @@ const PRE_RENAME_AGENTS_FILE = 'CLAUDE.md';
|
|
|
61
61
|
* of every call rather than of how to write one, and why large, escape-heavy
|
|
62
62
|
* and binary content goes by upload, which three write tools each said in
|
|
63
63
|
* full.
|
|
64
|
+
*
|
|
65
|
+
* 7,400 since the `__tmp__/` ref: four tools take it, so what it reaches and
|
|
66
|
+
* that it is read-only is said once here rather than in each description, and
|
|
67
|
+
* the section had no room left for it.
|
|
64
68
|
*/
|
|
65
|
-
export const SHARED_FILE_RULES_CAP =
|
|
69
|
+
export const SHARED_FILE_RULES_CAP = 7_400;
|
|
66
70
|
|
|
67
71
|
/** One shared rule: how the guide heads it, and the rule itself. */
|
|
68
72
|
export interface SharedFileRule {
|
|
@@ -114,6 +118,18 @@ export function sharedFileRules(layout: KbLayout): readonly SharedFileRule[] {
|
|
|
114
118
|
'`{ image_omitted, note }` stub instead, so read an image outside a chain when you mean to look at it. Any other ' +
|
|
115
119
|
'binary file reads as a one-line description rather than raw bytes.',
|
|
116
120
|
},
|
|
121
|
+
{
|
|
122
|
+
// What `__tmp__/` reaches and what it does not (see
|
|
123
|
+
// `workspace/workspace-tmp.ts`) — four tools take the ref, so it is a
|
|
124
|
+
// shared rule rather than a sentence in each description.
|
|
125
|
+
id: 'workspace-tmp',
|
|
126
|
+
heading: "Files staged in the workspace's tmp folder",
|
|
127
|
+
body:
|
|
128
|
+
"`__tmp__/<path>` names ONE file YOU staged in the branch workspace's `tmp/` folder (chat attachments land " +
|
|
129
|
+
'there; nobody else\'s staged files are reachable). read_file, file_stat, grep (`path`) and copy_file (`src`) ' +
|
|
130
|
+
'read it as a repository file; it is read-only and never listed — copy_file it to a repository path to keep ' +
|
|
131
|
+
'it. An entry stays up to seven days.',
|
|
132
|
+
},
|
|
117
133
|
{
|
|
118
134
|
id: 'write-mode',
|
|
119
135
|
heading: 'What a write may do at a path',
|