@bevel-software/platform-core-backend 0.22.0 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/core-ports.d.ts +11 -1
- 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 +3 -3
- package/dist/core/create-core-server.d.ts.map +1 -1
- package/dist/core/create-core-server.js +30 -11
- package/dist/core/create-core-server.js.map +1 -1
- package/dist/core/create-core-services.d.ts +9 -2
- package/dist/core/create-core-services.d.ts.map +1 -1
- package/dist/core/create-core-services.js +112 -23
- package/dist/core/create-core-services.js.map +1 -1
- package/dist/core/lifecycle.d.ts +34 -1
- package/dist/core/lifecycle.d.ts.map +1 -1
- package/dist/core/lifecycle.js +89 -13
- package/dist/core/lifecycle.js.map +1 -1
- package/dist/core-config.d.ts +0 -12
- package/dist/core-config.d.ts.map +1 -1
- package/dist/core-config.js +11 -13
- package/dist/core-config.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/modules/access/access-control.interface.d.ts +42 -12
- package/dist/modules/access/access-control.interface.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.d.ts +67 -10
- package/dist/modules/access/access-control.service.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.js +214 -35
- package/dist/modules/access/access-control.service.js.map +1 -1
- package/dist/modules/access/access.routes.d.ts.map +1 -1
- package/dist/modules/access/access.routes.js +13 -15
- package/dist/modules/access/access.routes.js.map +1 -1
- package/dist/modules/auth/account-admission.d.ts +58 -7
- package/dist/modules/auth/account-admission.d.ts.map +1 -1
- package/dist/modules/auth/account-admission.js +44 -1
- package/dist/modules/auth/account-admission.js.map +1 -1
- package/dist/modules/auth/account.routes.d.ts +1 -1
- package/dist/modules/auth/account.routes.d.ts.map +1 -1
- package/dist/modules/auth/account.routes.js +61 -5
- package/dist/modules/auth/account.routes.js.map +1 -1
- package/dist/modules/auth/auth.middleware.d.ts +1 -1
- package/dist/modules/auth/auth.middleware.d.ts.map +1 -1
- package/dist/modules/auth/auth.middleware.js +18 -7
- package/dist/modules/auth/auth.middleware.js.map +1 -1
- package/dist/modules/auth/auth.routes.d.ts.map +1 -1
- package/dist/modules/auth/auth.routes.js +8 -0
- package/dist/modules/auth/auth.routes.js.map +1 -1
- package/dist/modules/auth/auth.service.d.ts +70 -2
- package/dist/modules/auth/auth.service.d.ts.map +1 -1
- package/dist/modules/auth/auth.service.js +176 -7
- package/dist/modules/auth/auth.service.js.map +1 -1
- package/dist/modules/auth/oidc-auth-provider.d.ts.map +1 -1
- package/dist/modules/auth/oidc-auth-provider.js +7 -2
- package/dist/modules/auth/oidc-auth-provider.js.map +1 -1
- package/dist/modules/database/core-schema.d.ts +41 -24
- package/dist/modules/database/core-schema.d.ts.map +1 -1
- package/dist/modules/database/core-schema.js +35 -27
- package/dist/modules/database/core-schema.js.map +1 -1
- package/dist/modules/kb-fs/branch-name.d.ts.map +1 -1
- package/dist/modules/kb-fs/branch-name.js +12 -2
- package/dist/modules/kb-fs/branch-name.js.map +1 -1
- package/dist/modules/kb-fs/repo-path.d.ts +11 -16
- package/dist/modules/kb-fs/repo-path.d.ts.map +1 -1
- package/dist/modules/kb-fs/repo-path.js +79 -0
- package/dist/modules/kb-fs/repo-path.js.map +1 -1
- package/dist/modules/kb-sync/kb-sync.routes.d.ts +2 -2
- package/dist/modules/kb-sync/kb-sync.routes.d.ts.map +1 -1
- package/dist/modules/kb-sync/kb-sync.routes.js +14 -3
- package/dist/modules/kb-sync/kb-sync.routes.js.map +1 -1
- package/dist/modules/kb-sync/sync-auth.d.ts +3 -1
- package/dist/modules/kb-sync/sync-auth.d.ts.map +1 -1
- package/dist/modules/kb-sync/sync-auth.js +1 -1
- package/dist/modules/kb-sync/sync-auth.js.map +1 -1
- package/dist/modules/mcp/mcp-auth.middleware.d.ts.map +1 -1
- package/dist/modules/mcp/mcp-auth.middleware.js +21 -6
- package/dist/modules/mcp/mcp-auth.middleware.js.map +1 -1
- package/dist/modules/mcp/oauth/bevel-oauth-provider.d.ts.map +1 -1
- package/dist/modules/mcp/oauth/bevel-oauth-provider.js +3 -2
- package/dist/modules/mcp/oauth/bevel-oauth-provider.js.map +1 -1
- package/dist/modules/settings/deployment-settings.service.d.ts.map +1 -1
- package/dist/modules/settings/deployment-settings.service.js +8 -3
- package/dist/modules/settings/deployment-settings.service.js.map +1 -1
- package/dist/modules/settings/setup.routes.d.ts +52 -1
- package/dist/modules/settings/setup.routes.d.ts.map +1 -1
- package/dist/modules/settings/setup.routes.js +207 -19
- package/dist/modules/settings/setup.routes.js.map +1 -1
- package/dist/modules/skills/skills.contract.d.ts +90 -18
- package/dist/modules/skills/skills.contract.d.ts.map +1 -1
- package/dist/modules/skills/skills.contract.js +4 -1
- package/dist/modules/skills/skills.contract.js.map +1 -1
- package/dist/modules/skills/skills.service.d.ts +98 -9
- package/dist/modules/skills/skills.service.d.ts.map +1 -1
- package/dist/modules/skills/skills.service.js +239 -37
- package/dist/modules/skills/skills.service.js.map +1 -1
- package/dist/modules/skills/skills.tools.d.ts +7 -0
- package/dist/modules/skills/skills.tools.d.ts.map +1 -1
- package/dist/modules/skills/skills.tools.js +71 -7
- package/dist/modules/skills/skills.tools.js.map +1 -1
- 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 -2
- package/dist/modules/tool-auth/external-api-key.service.js.map +1 -1
- package/dist/modules/tool-auth/internal-token.service.d.ts +3 -3
- package/dist/modules/tool-auth/tool-auth.middleware.d.ts +16 -7
- package/dist/modules/tool-auth/tool-auth.middleware.d.ts.map +1 -1
- package/dist/modules/tool-auth/tool-auth.middleware.js +34 -12
- package/dist/modules/tool-auth/tool-auth.middleware.js.map +1 -1
- package/dist/modules/tool-helpers/tool-handler.d.ts +2 -1
- package/dist/modules/tool-helpers/tool-handler.d.ts.map +1 -1
- package/dist/modules/tool-helpers/tool-handler.js +25 -4
- package/dist/modules/tool-helpers/tool-handler.js.map +1 -1
- package/dist/modules/tool-helpers/tool.contract.d.ts +3 -2
- 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.js +1 -1
- package/dist/modules/tool-helpers/validate-token.js.map +1 -1
- package/dist/modules/workflow/agent-tools/change-request-summary.d.ts +98 -0
- package/dist/modules/workflow/agent-tools/change-request-summary.d.ts.map +1 -0
- package/dist/modules/workflow/agent-tools/change-request-summary.js +81 -0
- package/dist/modules/workflow/agent-tools/change-request-summary.js.map +1 -0
- package/dist/modules/workflow/agent-tools/workflow.tools.d.ts.map +1 -1
- package/dist/modules/workflow/agent-tools/workflow.tools.js +75 -35
- package/dist/modules/workflow/agent-tools/workflow.tools.js.map +1 -1
- package/dist/modules/workflow/file-lock.service.d.ts +24 -0
- package/dist/modules/workflow/file-lock.service.d.ts.map +1 -1
- package/dist/modules/workflow/file-lock.service.js +30 -0
- package/dist/modules/workflow/file-lock.service.js.map +1 -1
- package/dist/modules/workflow/git/git.service.d.ts +69 -0
- package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
- package/dist/modules/workflow/git/git.service.js +153 -2
- package/dist/modules/workflow/git/git.service.js.map +1 -1
- package/dist/modules/workflow/pending-commits.service.d.ts +38 -0
- package/dist/modules/workflow/pending-commits.service.d.ts.map +1 -1
- package/dist/modules/workflow/pending-commits.service.js +55 -0
- package/dist/modules/workflow/pending-commits.service.js.map +1 -1
- package/dist/modules/workflow/workflow-hooks.d.ts +54 -32
- package/dist/modules/workflow/workflow-hooks.d.ts.map +1 -1
- package/dist/modules/workflow/workflow-hooks.js +16 -1
- package/dist/modules/workflow/workflow-hooks.js.map +1 -1
- package/dist/modules/workflow/workflow.service.d.ts +60 -6
- package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
- package/dist/modules/workflow/workflow.service.js +111 -4
- package/dist/modules/workflow/workflow.service.js.map +1 -1
- package/dist/modules/workspace/agent-access.gate.d.ts +94 -0
- package/dist/modules/workspace/agent-access.gate.d.ts.map +1 -0
- package/dist/modules/workspace/agent-access.gate.js +123 -0
- package/dist/modules/workspace/agent-access.gate.js.map +1 -0
- package/dist/modules/workspace/routine-write-policy.d.ts +5 -6
- package/dist/modules/workspace/routine-write-policy.d.ts.map +1 -1
- package/dist/modules/workspace/routine-write-policy.js +5 -6
- package/dist/modules/workspace/routine-write-policy.js.map +1 -1
- package/dist/modules/workspace/session-sink.d.ts +5 -5
- package/dist/modules/workspace/set-aside-clone.d.ts +46 -0
- package/dist/modules/workspace/set-aside-clone.d.ts.map +1 -0
- package/dist/modules/workspace/set-aside-clone.js +92 -0
- package/dist/modules/workspace/set-aside-clone.js.map +1 -0
- package/dist/modules/workspace/startup/kb-startup-runner.d.ts +59 -9
- package/dist/modules/workspace/startup/kb-startup-runner.d.ts.map +1 -1
- package/dist/modules/workspace/startup/kb-startup-runner.js +65 -24
- package/dist/modules/workspace/startup/kb-startup-runner.js.map +1 -1
- package/dist/modules/workspace/workspace.routes.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.routes.js +92 -3
- package/dist/modules/workspace/workspace.routes.js.map +1 -1
- package/dist/modules/workspace/workspace.service.d.ts +115 -5
- package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.service.js +255 -25
- package/dist/modules/workspace/workspace.service.js.map +1 -1
- package/dist/modules/workspace/workspace.tools.d.ts +2 -2
- package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.tools.js +309 -115
- package/dist/modules/workspace/workspace.tools.js.map +1 -1
- package/dist/modules/write-access/write-access.d.ts +60 -0
- package/dist/modules/write-access/write-access.d.ts.map +1 -0
- package/dist/modules/write-access/write-access.js +129 -0
- package/dist/modules/write-access/write-access.js.map +1 -0
- package/dist/shared/domain-errors.d.ts +25 -0
- package/dist/shared/domain-errors.d.ts.map +1 -1
- package/dist/shared/domain-errors.js +28 -0
- package/dist/shared/domain-errors.js.map +1 -1
- package/dist/shared/git.contract.d.ts +20 -0
- package/dist/shared/git.contract.d.ts.map +1 -1
- package/dist/shared/git.contract.js +26 -0
- package/dist/shared/git.contract.js.map +1 -1
- package/dist/tenancy/static-tenant-source.d.ts +0 -1
- package/dist/tenancy/static-tenant-source.d.ts.map +1 -1
- package/dist/tenancy/static-tenant-source.js +0 -2
- package/dist/tenancy/static-tenant-source.js.map +1 -1
- package/migrations/0014_change_request_closed_reason.sql +1 -0
- package/migrations/0015_account_deactivation.sql +3 -0
- package/migrations/meta/0014_snapshot.json +2265 -0
- package/migrations/meta/0015_snapshot.json +2271 -0
- package/migrations/meta/_journal.json +14 -0
- package/package.json +3 -3
- package/src/__tests__/kb-layout-config.test.ts +0 -2
- package/src/__tests__/retired-settings.test.ts +96 -0
- package/src/core/__tests__/gated-boot.test.ts +132 -0
- package/src/core/__tests__/lifecycle.test.ts +170 -1
- package/src/core/__tests__/set-aside-root-is-one-place.test.ts +63 -0
- package/src/core/core-ports.ts +11 -1
- package/src/core/create-core-server.ts +32 -14
- package/src/core/create-core-services.ts +129 -23
- package/src/core/lifecycle.ts +98 -13
- package/src/core-config.ts +13 -14
- package/src/index.ts +12 -1
- package/src/modules/access/__tests__/access-control.preview-relocation.test.ts +385 -0
- package/src/modules/access/__tests__/access-control.prospective.test.ts +94 -18
- package/src/modules/access/__tests__/access.routes.prospective.test.ts +6 -7
- package/src/modules/access/access-control.interface.ts +43 -12
- package/src/modules/access/access-control.service.ts +234 -36
- package/src/modules/access/access.routes.ts +13 -15
- package/src/modules/agent-instructions/__tests__/agent-instructions.route.test.ts +3 -1
- package/src/modules/auth/__tests__/account-deactivation.test.ts +252 -0
- package/src/modules/auth/__tests__/account.routes.test.ts +81 -3
- package/src/modules/auth/__tests__/auth.middleware.test.ts +45 -22
- package/src/modules/auth/__tests__/auth.service.test.ts +1 -1
- package/src/modules/auth/account-admission.ts +78 -9
- package/src/modules/auth/account.routes.ts +63 -7
- package/src/modules/auth/auth.middleware.ts +19 -8
- package/src/modules/auth/auth.routes.ts +8 -0
- package/src/modules/auth/auth.service.ts +179 -6
- package/src/modules/auth/oidc-auth-provider.ts +7 -2
- package/src/modules/database/core-schema.ts +35 -27
- package/src/modules/kb-fs/__tests__/branch-name.test.ts +10 -0
- package/src/modules/kb-fs/__tests__/repo-path.test.ts +123 -0
- package/src/modules/kb-fs/branch-name.ts +14 -1
- package/src/modules/kb-fs/repo-path.ts +85 -0
- package/src/modules/kb-sync/__tests__/kb-sync.routes.test.ts +26 -1
- package/src/modules/kb-sync/kb-sync.routes.ts +14 -4
- package/src/modules/kb-sync/sync-auth.ts +2 -2
- package/src/modules/mcp/__tests__/mcp-auth.middleware.test.ts +60 -3
- package/src/modules/mcp/__tests__/mcp.service.test.ts +68 -6
- package/src/modules/mcp/mcp-auth.middleware.ts +20 -6
- package/src/modules/mcp/oauth/bevel-oauth-provider.ts +3 -1
- package/src/modules/plugins/__tests__/plugins.tools.test.ts +2 -1
- package/src/modules/settings/__tests__/deployment-settings.service.test.ts +16 -0
- package/src/modules/settings/__tests__/setup.routes.git-mode.test.ts +91 -58
- package/src/modules/settings/__tests__/setup.routes.github-app.test.ts +25 -8
- package/src/modules/settings/__tests__/setup.routes.managed-phase.test.ts +14 -11
- package/src/modules/settings/__tests__/setup.routes.repository-change.test.ts +390 -0
- package/src/modules/settings/__tests__/setup.routes.test.ts +3 -0
- package/src/modules/settings/deployment-settings.service.ts +8 -3
- package/src/modules/settings/setup.routes.ts +263 -19
- package/src/modules/skills/__tests__/allowed-tools-warn.tools.test.ts +2 -1
- package/src/modules/skills/__tests__/branch-skills.tools.test.ts +218 -0
- package/src/modules/skills/__tests__/skills.service.test.ts +229 -5
- package/src/modules/skills/skills.contract.ts +91 -18
- package/src/modules/skills/skills.service.ts +278 -41
- package/src/modules/skills/skills.tools.ts +80 -8
- package/src/modules/tool-auth/__tests__/manual-auth.middleware.test.ts +14 -1
- package/src/modules/tool-auth/external-api-key.service.ts +4 -2
- package/src/modules/tool-auth/internal-token.service.ts +3 -3
- package/src/modules/tool-auth/tool-auth.middleware.ts +34 -11
- package/src/modules/tool-helpers/__tests__/agent-roles-write.test.ts +5 -3
- package/src/modules/tool-helpers/__tests__/phase4-tools.test.ts +3 -3
- package/src/modules/tool-helpers/__tests__/validate-token.test.ts +11 -1
- package/src/modules/tool-helpers/tool-handler.ts +24 -4
- package/src/modules/tool-helpers/tool.contract.ts +3 -2
- package/src/modules/tool-helpers/validate-token.ts +1 -1
- package/src/modules/workflow/__tests__/pending-commits.repository-replaced.test.ts +111 -0
- package/src/modules/workflow/__tests__/workflow.routes.history-read-gate.test.ts +25 -0
- package/src/modules/workflow/__tests__/workflow.service.repository-replaced.test.ts +249 -0
- package/src/modules/workflow/agent-tools/__tests__/workflow.tools.test.ts +264 -6
- package/src/modules/workflow/agent-tools/change-request-summary.ts +182 -0
- package/src/modules/workflow/agent-tools/workflow.tools.ts +86 -35
- package/src/modules/workflow/file-lock.service.ts +31 -0
- package/src/modules/workflow/git/__tests__/git.service.fileBytesAtCommit.test.ts +260 -0
- package/src/modules/workflow/git/git.service.ts +168 -0
- package/src/modules/workflow/pending-commits.service.ts +59 -1
- package/src/modules/workflow/workflow-hooks.ts +64 -26
- package/src/modules/workflow/workflow.service.ts +121 -4
- package/src/modules/workspace/__tests__/agent-access.gate.test.ts +208 -0
- package/src/modules/workspace/__tests__/escape-sequences.routes.test.ts +384 -0
- package/src/modules/workspace/__tests__/file-stat-access.test.ts +2 -1
- package/src/modules/workspace/__tests__/git-internals.security.test.ts +2 -1
- package/src/modules/workspace/__tests__/set-aside-clone.test.ts +52 -0
- package/src/modules/workspace/__tests__/workspace.routes.at-ref.test.ts +305 -0
- package/src/modules/workspace/__tests__/workspace.routes.download.test.ts +2 -0
- package/src/modules/workspace/__tests__/workspace.service.forget-clone-races.test.ts +147 -0
- package/src/modules/workspace/__tests__/workspace.service.replaced-repository.test.ts +326 -0
- package/src/modules/workspace/__tests__/workspace.service.test.ts +20 -9
- package/src/modules/workspace/__tests__/workspace.tools.agents-file.test.ts +2 -3
- package/src/modules/workspace/__tests__/workspace.tools.branch-errors.test.ts +2 -3
- package/src/modules/workspace/__tests__/workspace.tools.test.ts +685 -14
- package/src/modules/workspace/agent-access.gate.ts +164 -0
- package/src/modules/workspace/routine-write-policy.ts +5 -6
- package/src/modules/workspace/session-sink.ts +5 -5
- package/src/modules/workspace/set-aside-clone.ts +96 -0
- package/src/modules/workspace/startup/__tests__/kb-startup-runner.test.ts +86 -0
- package/src/modules/workspace/startup/kb-startup-runner.ts +115 -34
- package/src/modules/workspace/workspace.routes.ts +95 -3
- package/src/modules/workspace/workspace.service.ts +256 -24
- package/src/modules/workspace/workspace.tools.ts +338 -119
- package/src/modules/write-access/__tests__/write-access.test.ts +248 -0
- package/src/modules/write-access/write-access.ts +153 -0
- package/src/shared/domain-errors.ts +31 -0
- package/src/shared/git.contract.ts +27 -0
- package/src/tenancy/__tests__/static-tenant-source.test.ts +0 -1
- package/src/tenancy/static-tenant-source.ts +0 -3
- package/dist/modules/workflow/session-ontology.policy.d.ts +0 -52
- package/dist/modules/workflow/session-ontology.policy.d.ts.map +0 -1
- package/dist/modules/workflow/session-ontology.policy.js +0 -62
- package/dist/modules/workflow/session-ontology.policy.js.map +0 -1
- package/dist/modules/workflow/session-ontology.service.d.ts +0 -105
- package/dist/modules/workflow/session-ontology.service.d.ts.map +0 -1
- package/dist/modules/workflow/session-ontology.service.js +0 -147
- package/dist/modules/workflow/session-ontology.service.js.map +0 -1
- package/dist/modules/workspace/session-ontology.gate.d.ts +0 -114
- package/dist/modules/workspace/session-ontology.gate.d.ts.map +0 -1
- package/dist/modules/workspace/session-ontology.gate.js +0 -161
- package/dist/modules/workspace/session-ontology.gate.js.map +0 -1
- package/dist/shared/kb-layout.d.ts +0 -39
- package/dist/shared/kb-layout.d.ts.map +0 -1
- package/dist/shared/kb-layout.js +0 -103
- package/dist/shared/kb-layout.js.map +0 -1
- package/dist/shared/kb-layout.test.d.ts +0 -2
- package/dist/shared/kb-layout.test.d.ts.map +0 -1
- package/dist/shared/kb-layout.test.js +0 -75
- package/dist/shared/kb-layout.test.js.map +0 -1
- package/src/modules/workflow/__tests__/session-ontology.policy.test.ts +0 -62
- package/src/modules/workflow/__tests__/session-ontology.service.test.ts +0 -201
- package/src/modules/workflow/session-ontology.policy.ts +0 -70
- package/src/modules/workflow/session-ontology.service.ts +0 -183
- package/src/modules/workspace/__tests__/session-ontology.gate.test.ts +0 -239
- package/src/modules/workspace/session-ontology.gate.ts +0 -191
- package/src/shared/kb-layout.test.ts +0 -98
- package/src/shared/kb-layout.ts +0 -102
|
@@ -37,7 +37,7 @@ import {
|
|
|
37
37
|
registerPluginsTools,
|
|
38
38
|
} from '../modules/plugins/index.js';
|
|
39
39
|
import { keyOrSessionAuth } from '../modules/tool-auth/key-or-session.middleware.js';
|
|
40
|
-
import type
|
|
40
|
+
import { ToolDescriptionNotes, type AgentAccessGate } from '../modules/workspace/agent-access.gate.js';
|
|
41
41
|
import {
|
|
42
42
|
createSecretsVaultRoutes,
|
|
43
43
|
createSecretsVaultPublicRoutes,
|
|
@@ -72,6 +72,7 @@ import { publicConfig } from './public-config.js';
|
|
|
72
72
|
import { createReadiness } from './readiness.js';
|
|
73
73
|
import { createAgentInstructionsRoutes } from '../modules/agent-instructions/index.js';
|
|
74
74
|
import type { CoreServices } from './create-core-services.js';
|
|
75
|
+
import { createWriteAccessRoutes, createWriteGateMiddleware } from '../modules/write-access/write-access.js';
|
|
75
76
|
|
|
76
77
|
type ExpressApp = ReturnType<typeof express>;
|
|
77
78
|
|
|
@@ -85,8 +86,8 @@ export interface ToolSurfaceCtx {
|
|
|
85
86
|
router: Router;
|
|
86
87
|
toolAuth: RequestHandler;
|
|
87
88
|
toolHandler: CoreServices['toolHandlerFactory'];
|
|
88
|
-
/** Shared
|
|
89
|
-
|
|
89
|
+
/** Shared agent-access gate config (file tools + graph tools). */
|
|
90
|
+
agentAccessGate: AgentAccessGate;
|
|
90
91
|
core: CoreServices;
|
|
91
92
|
}
|
|
92
93
|
|
|
@@ -200,6 +201,14 @@ export async function createCoreServer(
|
|
|
200
201
|
// is mounted again below, once the body is parsed and once the caller is
|
|
201
202
|
// known (see the two mounts under `/api/workspace/:id`).
|
|
202
203
|
app.use('/api/workspace/:id', createGitInternalsRouteGuard(core.workspaceService));
|
|
204
|
+
// The read-only gate, ahead of every route — core's, the tool surface's
|
|
205
|
+
// and an overlay's alike — so a read-only deployment refuses a change
|
|
206
|
+
// wherever it would enter. It lets through what signs people in, manages
|
|
207
|
+
// accounts and configures the deployment (see `ALWAYS_WRITABLE`); write
|
|
208
|
+
// tools are judged by the tool layer. A no-op unless a host fills
|
|
209
|
+
// `ports.writeAccess`. Ahead of the body parser too: a refused write is
|
|
210
|
+
// refused with its own 403, never parsed first (and answered 400 or 413).
|
|
211
|
+
app.use(createWriteGateMiddleware(core.writeAccess));
|
|
203
212
|
app.use((req, res, next) => {
|
|
204
213
|
if (jsonExemptPaths.has(req.path) || isSyncRawBodyPath(req.path)) return next();
|
|
205
214
|
return globalJson(req, res, next);
|
|
@@ -420,24 +429,23 @@ export async function createCoreServer(
|
|
|
420
429
|
},
|
|
421
430
|
}),
|
|
422
431
|
);
|
|
423
|
-
// Shared
|
|
424
|
-
//
|
|
425
|
-
//
|
|
426
|
-
//
|
|
427
|
-
//
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
enabled: core.config.ontologySessionBlock,
|
|
431
|
-
kb: core.kb,
|
|
432
|
+
// Shared agent-access gate config, consumed by every tool surface that
|
|
433
|
+
// touches the KB (file tools + graph tools). The gate decides nothing
|
|
434
|
+
// itself: it calls the read and write hooks a deployment registered on
|
|
435
|
+
// `workflowService.hooks`, and carries the notes that deployment wants
|
|
436
|
+
// agents to read about the gated tools. Core registers neither, so a
|
|
437
|
+
// core-only deployment refuses nothing and says nothing extra.
|
|
438
|
+
const agentAccessGate: AgentAccessGate = {
|
|
432
439
|
recoveryBotEmail: RECOVERY_BOT_EMAIL,
|
|
433
440
|
hooks: core.workflowService.hooks,
|
|
441
|
+
notes: new ToolDescriptionNotes(),
|
|
434
442
|
};
|
|
435
443
|
// A skill's `allowed-tools`, checked against what the caller can see — on
|
|
436
444
|
// every save surface (agent write tools, the app's PUT /file) and on
|
|
437
445
|
// `get_skill`. Warnings only; it never refuses a save.
|
|
438
446
|
const allowedToolsChecker = new AllowedToolsChecker(core.toolRegistry, core.toolManualService, core.kb);
|
|
439
447
|
registerWorkflowTools(core.toolRegistry, toolsRouter, ta, th, core.kb);
|
|
440
|
-
registerWorkspaceTools(core.toolRegistry, toolsRouter, ta, th, core.spillStore, core.docExtractService, core.accessControl, core.kb,
|
|
448
|
+
registerWorkspaceTools(core.toolRegistry, toolsRouter, ta, th, core.spillStore, core.docExtractService, core.accessControl, core.kb, agentAccessGate, core.routineWritePolicy, core.sessionSink, allowedToolsChecker, core.changeGate);
|
|
441
449
|
registerSkillsTools(core.toolRegistry, toolsRouter, ta, th, core.skillService, allowedToolsChecker);
|
|
442
450
|
// Definitions only: the endpoints they describe are the app's own plugin
|
|
443
451
|
// creation routes, mounted below behind the key-or-session gate.
|
|
@@ -455,7 +463,7 @@ export async function createCoreServer(
|
|
|
455
463
|
router: toolsRouter,
|
|
456
464
|
toolAuth: ta,
|
|
457
465
|
toolHandler: th,
|
|
458
|
-
|
|
466
|
+
agentAccessGate,
|
|
459
467
|
core,
|
|
460
468
|
});
|
|
461
469
|
toolsRouter.use(createManualRoutes(
|
|
@@ -546,6 +554,7 @@ export async function createCoreServer(
|
|
|
546
554
|
// request also gets the resolved form judged — a link in the repository that
|
|
547
555
|
// points into the git folder — before any read gate or lock.
|
|
548
556
|
app.use('/api/workspace/:id', core.authMiddleware, createGitInternalsRouteGuard(core.workspaceService));
|
|
557
|
+
app.use('/api', core.authMiddleware, createWriteAccessRoutes(core.writeAccess));
|
|
549
558
|
app.use('/api', core.authMiddleware, createWorkspaceRoutes(
|
|
550
559
|
core.workspaceService,
|
|
551
560
|
core.authService,
|
|
@@ -722,6 +731,15 @@ export async function createCoreServer(
|
|
|
722
731
|
ensureManaged: (branch) => core.managedRepository.ensure(core.gitRunner, branch),
|
|
723
732
|
githubApp: core.githubApp,
|
|
724
733
|
},
|
|
734
|
+
// What a move to another repository has to decide about, and what it
|
|
735
|
+
// runs under. Bound here rather than handed the whole workflow service:
|
|
736
|
+
// the setup routes ask two questions, hold the commit worker for the
|
|
737
|
+
// move, and know nothing else about a request or a commit.
|
|
738
|
+
{
|
|
739
|
+
countOpen: () => core.workflowService.countOpenChangeRequests(),
|
|
740
|
+
closeAsRepositoryReplaced: () => core.workflowService.closeOpenChangeRequestsAsRepositoryReplaced(),
|
|
741
|
+
whileCommitsHeld: (work) => core.whileCommitsHeld(work),
|
|
742
|
+
},
|
|
725
743
|
),
|
|
726
744
|
);
|
|
727
745
|
// Connecting a repository on GitHub through a GitHub App: the round trips
|
|
@@ -12,6 +12,7 @@ import { sql } from 'drizzle-orm';
|
|
|
12
12
|
import { DEFAULT_DB_SCHEMA, assertSearchPath } from '../modules/database/connection.js';
|
|
13
13
|
import { DEFAULT_SECRETS_SCOPE } from '../modules/secrets-vault/secrets-variable-loader.js';
|
|
14
14
|
import { KbContext } from '../shared/kb-context.js';
|
|
15
|
+
import { branchForWorkspaceId } from '../shared/workspace-id.js';
|
|
15
16
|
import { getDb, type Database } from '../modules/database/connection.js';
|
|
16
17
|
import { runCoreMigrations } from '../modules/database/migrate.js';
|
|
17
18
|
import { coreMigrationsDir } from '../assets.js';
|
|
@@ -35,7 +36,7 @@ import { ManagedRepository, MANAGED_DEFAULT_BRANCH } from '../modules/settings/m
|
|
|
35
36
|
import { RepositorySource } from '../modules/settings/repository-source.js';
|
|
36
37
|
import { GitHubAppConnection } from '../modules/github-app/index.js';
|
|
37
38
|
import { AdvisoryLease, AdvisoryLock } from '../modules/database/advisory-lock.js';
|
|
38
|
-
import { holdCommitWorkerLease, withStartupTask, type LeaseLoopHandle } from './lifecycle.js';
|
|
39
|
+
import { holdCommitWorkerLease, holdable, withStartupTask, type LeaseLoopHandle } from './lifecycle.js';
|
|
39
40
|
|
|
40
41
|
/** The hosted MCP endpoint at a deployment address, with any userinfo stripped. */
|
|
41
42
|
function mcpEndpointUrl(publicBackendUrl: string): string {
|
|
@@ -110,7 +111,6 @@ import { WorkflowEventBus } from '../modules/workflow/event-bus.js';
|
|
|
110
111
|
import { FileChangeNotifier } from '../modules/kb-fs/file-change-notifier.js';
|
|
111
112
|
import { WorkflowService } from '../modules/workflow/workflow.service.js';
|
|
112
113
|
import { WorkflowHooks } from '../modules/workflow/workflow-hooks.js';
|
|
113
|
-
import { SessionOntologyService } from '../modules/workflow/session-ontology.service.js';
|
|
114
114
|
import { PendingCommitsService } from '../modules/workflow/pending-commits.service.js';
|
|
115
115
|
import {
|
|
116
116
|
PendingCommitsWorker,
|
|
@@ -142,6 +142,7 @@ import { BevelOAuthProvider } from '../modules/mcp/oauth/bevel-oauth-provider.js
|
|
|
142
142
|
import { getOAuthProtectedResourceMetadataUrl } from '@modelcontextprotocol/sdk/server/auth/router.js';
|
|
143
143
|
import { ToolRegistry } from '../modules/tool-registry/tool-registry.js';
|
|
144
144
|
import { createToolContextResolver } from '../modules/tool-helpers/tool-context.js';
|
|
145
|
+
import { alwaysWritable, type IWriteAccess } from '../modules/write-access/write-access.js';
|
|
145
146
|
import { createToolHandlerFactory } from '../modules/tool-helpers/tool-handler.js';
|
|
146
147
|
import { TokenCrypto } from '../shared/token-crypto.js';
|
|
147
148
|
import { UpdateCheckService } from '../modules/update-check/update-check.service.js';
|
|
@@ -182,6 +183,12 @@ export interface CoreServices {
|
|
|
182
183
|
* lease to the replacement.
|
|
183
184
|
*/
|
|
184
185
|
commitWorker: LeaseLoopHandle;
|
|
186
|
+
/**
|
|
187
|
+
* Run something with no queued commit being written: the commit worker is
|
|
188
|
+
* stopped for its duration and started again afterwards, the lease kept.
|
|
189
|
+
* What a move to another repository runs the startup phase under.
|
|
190
|
+
*/
|
|
191
|
+
whileCommitsHeld<T>(work: () => Promise<T>): Promise<T>;
|
|
185
192
|
workspaceService: WorkspaceService;
|
|
186
193
|
/**
|
|
187
194
|
* The KB startup phase (see `startup/on-server-start.ts`): run at the
|
|
@@ -218,7 +225,6 @@ export interface CoreServices {
|
|
|
218
225
|
creatorAccess: CreatorAccessService;
|
|
219
226
|
/** Read-before-write, for the surfaces that ask ahead of the lock (see `access-model/change-gate.ts`). */
|
|
220
227
|
changeGate: ChangeReadGate;
|
|
221
|
-
sessionOntologyService: SessionOntologyService;
|
|
222
228
|
routineWritePolicy: RoutineWritePolicyService;
|
|
223
229
|
skillService: SkillService;
|
|
224
230
|
pendingSkillsService: PendingSkillsService;
|
|
@@ -327,6 +333,8 @@ export interface CoreServices {
|
|
|
327
333
|
toolAuthMiddleware: ReturnType<typeof createToolAuthMiddleware>;
|
|
328
334
|
manualAuthMiddleware: ReturnType<typeof createManualAuthMiddleware>;
|
|
329
335
|
toolHandlerFactory: ReturnType<typeof createToolHandlerFactory>;
|
|
336
|
+
/** Whether the deployment may be changed right now — `ports.writeAccess`, or always. */
|
|
337
|
+
writeAccess: IWriteAccess;
|
|
330
338
|
// ── Server-time seams (enterprise overwrites after construction) ────────
|
|
331
339
|
/** `start_session` backing — core default {@link UuidSessionSink}. */
|
|
332
340
|
sessionSink: ISessionSink;
|
|
@@ -486,12 +494,83 @@ export async function createCoreServices(
|
|
|
486
494
|
if (repositorySource.mode() === 'github-app') {
|
|
487
495
|
await githubApp.prepare().catch(() => undefined);
|
|
488
496
|
}
|
|
497
|
+
/**
|
|
498
|
+
* Where a working copy of a replaced repository is kept, for BOTH the places
|
|
499
|
+
* that find one: the KB startup phase sweeping the workspaces root, and the
|
|
500
|
+
* workspace service refusing to adopt one on a branch open. One variable,
|
|
501
|
+
* passed to both, because they move the same clones for the same reason and
|
|
502
|
+
* an admin looking for their work must have one folder to look in — the two
|
|
503
|
+
* drifted apart once already, and the copy the workspace service set aside
|
|
504
|
+
* landed in the image's own filesystem and was gone at the next recreate.
|
|
505
|
+
*
|
|
506
|
+
* Under the backups root: a persistent volume of its own, and one nothing
|
|
507
|
+
* sweeps (see the runner's `reconcileClonesWithConfiguredRepository`).
|
|
508
|
+
*/
|
|
509
|
+
const replacedWorkingCopiesRoot = path.join(config.backupsRoot, 'replaced-working-copies');
|
|
510
|
+
/**
|
|
511
|
+
* What must leave with a working copy that is set aside, whoever sets it
|
|
512
|
+
* aside: the startup phase, or the workspace service on a branch open.
|
|
513
|
+
*
|
|
514
|
+
* Its queue: whatever still waits to be committed into that copy was
|
|
515
|
+
* written against the repository that was left, and the path it names is
|
|
516
|
+
* about to hold a fresh clone of another one. Held for a person, on every
|
|
517
|
+
* branch, never written and never deleted.
|
|
518
|
+
*
|
|
519
|
+
* And the locks on its branch: each is a claim on a file of the copy that
|
|
520
|
+
* is going. Left standing, a holder still connected keeps one alive with
|
|
521
|
+
* heartbeats, and the same path in the new repository refuses everybody
|
|
522
|
+
* else. Dropped without enqueueing anything, as when change requests are
|
|
523
|
+
* closed as "repository replaced".
|
|
524
|
+
*
|
|
525
|
+
* Throws when either cannot be done, and the caller then leaves the copy
|
|
526
|
+
* where it is. Reaches FORWARD to both services, like `gitService` below:
|
|
527
|
+
* it is only ever called long after this function has returned them.
|
|
528
|
+
*/
|
|
529
|
+
const releaseWorkOnSetAsideCopy = async (workspaceId: string): Promise<void> => {
|
|
530
|
+
const held = await pendingCommitsService.markNeedsAttentionInWorkspace(
|
|
531
|
+
workspaceId,
|
|
532
|
+
'The knowledge-base repository was replaced while this commit was still queued, so it was never ' +
|
|
533
|
+
'written. The bytes are kept here: the working copy it was meant for belongs to the previous repository.',
|
|
534
|
+
);
|
|
535
|
+
if (held > 0) {
|
|
536
|
+
logger('kb-startup').warn(
|
|
537
|
+
`${held} queued commit(s) for the working copy "${workspaceId}" need attention: the repository was ` +
|
|
538
|
+
'replaced before they landed, so they were not written to the new one.',
|
|
539
|
+
);
|
|
540
|
+
}
|
|
541
|
+
await fileLockService.releaseAllOnBranch(branchForWorkspaceId(workspaceId));
|
|
542
|
+
};
|
|
489
543
|
const workspaceService = new WorkspaceService(
|
|
490
544
|
config.workspacesRoot,
|
|
491
545
|
() => repositorySource.url(),
|
|
492
546
|
kb,
|
|
493
547
|
disk,
|
|
494
548
|
gitRunner,
|
|
549
|
+
replacedWorkingCopiesRoot,
|
|
550
|
+
// A branch opened onto a clone of another repository sets it aside on
|
|
551
|
+
// the spot, with nobody having stopped the commit worker for it. So it
|
|
552
|
+
// is held here, for the one move, and the copy's work leaves first and
|
|
553
|
+
// is looked for again once the copy has gone: a release that queued its
|
|
554
|
+
// bytes in between was writing to the copy that left.
|
|
555
|
+
//
|
|
556
|
+
// Only the first look may refuse the branch: the copy has not moved yet.
|
|
557
|
+
// The second runs after the move, so its failure is tried once more and
|
|
558
|
+
// then logged, as the startup phase logs the same step. Refusing then
|
|
559
|
+
// would report a move that did happen as one that did not.
|
|
560
|
+
(workspaceId, move) =>
|
|
561
|
+
leased.whileHeld(async () => {
|
|
562
|
+
await releaseWorkOnSetAsideCopy(workspaceId);
|
|
563
|
+
await move();
|
|
564
|
+
await releaseWorkOnSetAsideCopy(workspaceId)
|
|
565
|
+
.catch(() => releaseWorkOnSetAsideCopy(workspaceId))
|
|
566
|
+
.catch((err: unknown) => {
|
|
567
|
+
logger('workspace').error(
|
|
568
|
+
`the working copy "${workspaceId}" was set aside, but what was queued or locked on it meanwhile ` +
|
|
569
|
+
'could not be looked for again:',
|
|
570
|
+
{ err },
|
|
571
|
+
);
|
|
572
|
+
});
|
|
573
|
+
}),
|
|
495
574
|
);
|
|
496
575
|
// The KB startup phase: every seeding, scaffolding and migration concern,
|
|
497
576
|
// run through one runner at the deployment's quiet moments (boot + setup
|
|
@@ -529,10 +608,9 @@ export async function createCoreServices(
|
|
|
529
608
|
// the first thing that needs them.
|
|
530
609
|
kbRepoUrl: () => repositorySource.url(),
|
|
531
610
|
workspacesRoot: config.workspacesRoot,
|
|
532
|
-
//
|
|
533
|
-
//
|
|
534
|
-
|
|
535
|
-
setAsideRoot: path.join(config.backupsRoot, 'replaced-working-copies'),
|
|
611
|
+
// The same folder the workspace service sets aside into — see
|
|
612
|
+
// `replacedWorkingCopiesRoot` above for why the two must agree.
|
|
613
|
+
setAsideRoot: replacedWorkingCopiesRoot,
|
|
536
614
|
kbDirName,
|
|
537
615
|
templateDir: config.kbTemplateDir,
|
|
538
616
|
defaultBranch: () => kb.defaultBranch,
|
|
@@ -543,6 +621,33 @@ export async function createCoreServices(
|
|
|
543
621
|
steps: kbStartupSteps,
|
|
544
622
|
buildSeedTree: buildSeedTree(disk, config.kbTemplateDir, extraDirs, [config.adminEmail], kb),
|
|
545
623
|
gitRunner,
|
|
624
|
+
// The work on a working copy that is about to be set aside leaves first:
|
|
625
|
+
// its queued commits and the locks on its branch (see
|
|
626
|
+
// `releaseWorkOnSetAsideCopy`). A failure here stops the phase before
|
|
627
|
+
// the copy moves: a replacement cloned over an untouched queue is how
|
|
628
|
+
// those bytes would land in the wrong repository.
|
|
629
|
+
beforeCloneSetAside: releaseWorkOnSetAsideCopy,
|
|
630
|
+
// And once it is set aside it must leave the workspace service's cache:
|
|
631
|
+
// on the SAVE that moves the deployment the process is already running,
|
|
632
|
+
// and a cached path to a directory that is gone is how the next reader
|
|
633
|
+
// gets an ENOENT instead of a fresh clone.
|
|
634
|
+
//
|
|
635
|
+
// Then the copy's work is looked for once more. A release that queued
|
|
636
|
+
// its bytes between the first look and the move was writing to the copy
|
|
637
|
+
// that has now gone; nothing can queue against it after this.
|
|
638
|
+
onCloneDiscarded: async (workspaceId) => {
|
|
639
|
+
workspaceService.forgetClone(workspaceId);
|
|
640
|
+
await releaseWorkOnSetAsideCopy(workspaceId);
|
|
641
|
+
},
|
|
642
|
+
// And the replacement it cloned in its place: a fresh clone holds every
|
|
643
|
+
// ref, so the git layer's per-workspace fetch record is told so. Without
|
|
644
|
+
// it that record still holds the FAILED fetch of the repository that was
|
|
645
|
+
// replaced, and a strict branch listing within its TTL refuses the new
|
|
646
|
+
// clone's refs as unproven. Reaches FORWARD to `gitService`, which is
|
|
647
|
+
// built further down: safe because the runner only ever calls this from
|
|
648
|
+
// `runAll`, which the server builder invokes long after this function has
|
|
649
|
+
// returned every service.
|
|
650
|
+
onCloneCreated: (workspaceId) => gitService.noteWorkspaceFetched(workspaceId),
|
|
546
651
|
});
|
|
547
652
|
// Shared, workspace-independent store for oversized `call_tool_chain` results,
|
|
548
653
|
// read back via `read_file`. Sibling of `workspacesRoot`, never committed.
|
|
@@ -574,14 +679,10 @@ export async function createCoreServices(
|
|
|
574
679
|
// except that new folder at a root (see `access-model/change-gate.ts`).
|
|
575
680
|
const changeGate = new ChangeReadGate(workspaceService, accessControl, kb, disk);
|
|
576
681
|
|
|
577
|
-
// Ontology-session boundary: records each agent run's touched ontologies and
|
|
578
|
-
// blocks writes once a run has crossed ontologies. Postgres-backed so the
|
|
579
|
-
// boundary survives a restart.
|
|
580
|
-
const sessionOntologyService = new SessionOntologyService(db, kb);
|
|
581
682
|
// Per-run write restriction (by file extension). Shared by the workspace tool
|
|
582
683
|
// surface (which enforces it) and the routine runner (which sets it for
|
|
583
|
-
// dashboard-only `watchlist_check` runs). In-memory: a restriction lives only
|
|
584
|
-
// one run
|
|
684
|
+
// dashboard-only `watchlist_check` runs). In-memory: a restriction lives only
|
|
685
|
+
// for one run.
|
|
585
686
|
const routineWritePolicy = new RoutineWritePolicyService();
|
|
586
687
|
// Skills: discovered from the default-branch workspace only (global catalog).
|
|
587
688
|
const skillService = new SkillService(workspaceService, accessControl, kb, disk);
|
|
@@ -632,11 +733,11 @@ export async function createCoreServices(
|
|
|
632
733
|
// against each other — a backup-reseed races a concurrent commit otherwise.
|
|
633
734
|
const workspaceMutex = new WorkspaceMutex();
|
|
634
735
|
// Workflow lifecycle hooks — the ONE registry this composition shares
|
|
635
|
-
// between GitService (advisory commit validation), the
|
|
636
|
-
//
|
|
637
|
-
// registers module-owned handlers on `workflowService.hooks` right
|
|
638
|
-
// this function returns. Core registers none: no commit-time
|
|
639
|
-
// (advisory anyway) and
|
|
736
|
+
// between GitService (advisory commit validation), the agent-access gate
|
|
737
|
+
// (the blocking agentRead / preWrite hooks), and the composition root,
|
|
738
|
+
// which registers module-owned handlers on `workflowService.hooks` right
|
|
739
|
+
// after this function returns. Core registers none: no commit-time
|
|
740
|
+
// validation (advisory anyway) and nothing that refuses an agent call.
|
|
640
741
|
const workflowHooks = new WorkflowHooks();
|
|
641
742
|
const gitService = new GitService(
|
|
642
743
|
workspaceService,
|
|
@@ -731,7 +832,7 @@ export async function createCoreServices(
|
|
|
731
832
|
eventBus,
|
|
732
833
|
fileChangeNotifier,
|
|
733
834
|
// Exposed as `workflowService.hooks` — the SAME instance GitService and
|
|
734
|
-
// the
|
|
835
|
+
// the agent-access gate consult, so a deployment's registrations against
|
|
735
836
|
// it reach every hook point.
|
|
736
837
|
workflowHooks,
|
|
737
838
|
);
|
|
@@ -1134,8 +1235,9 @@ export async function createCoreServices(
|
|
|
1134
1235
|
creatorAccess,
|
|
1135
1236
|
loadActiveGroups,
|
|
1136
1237
|
});
|
|
1137
|
-
const
|
|
1138
|
-
const
|
|
1238
|
+
const writeAccess = ports.writeAccess ?? alwaysWritable;
|
|
1239
|
+
const toolHandlerFactory = createToolHandlerFactory(resolveToolContext, writeAccess);
|
|
1240
|
+
const toolAuthMiddleware = createToolAuthMiddleware(externalApiKeyService, internalTokenService, authService);
|
|
1139
1241
|
// Read-only manual endpoints accept the above PLUS a browser JWT, so a
|
|
1140
1242
|
// logged-in user can browse the catalog with their session. Execution routes
|
|
1141
1243
|
// keep `toolAuthMiddleware` (no JWT), so a session can read but not invoke.
|
|
@@ -1187,7 +1289,10 @@ export async function createCoreServices(
|
|
|
1187
1289
|
name: recoveryBot.name,
|
|
1188
1290
|
},
|
|
1189
1291
|
});
|
|
1190
|
-
|
|
1292
|
+
// Holdable: a move to another repository stops the worker for as long as
|
|
1293
|
+
// working copies are being set aside and cloned again, without giving up
|
|
1294
|
+
// the lease (see `holdable` in `core/lifecycle.ts`).
|
|
1295
|
+
const leased = holdable(withStartupTask(pendingCommitsWorker, reconcileQueue));
|
|
1191
1296
|
|
|
1192
1297
|
// SSO providers. The array REFERENCE is shared with the caller's port — an
|
|
1193
1298
|
// overlay pushes its own plugins into it after construction (they mount when
|
|
@@ -1283,6 +1388,7 @@ export async function createCoreServices(
|
|
|
1283
1388
|
db,
|
|
1284
1389
|
gitRunner,
|
|
1285
1390
|
commitWorker,
|
|
1391
|
+
whileCommitsHeld: (work) => leased.whileHeld(work),
|
|
1286
1392
|
startupRetry: null,
|
|
1287
1393
|
tenantKey,
|
|
1288
1394
|
secretsScope,
|
|
@@ -1302,7 +1408,6 @@ export async function createCoreServices(
|
|
|
1302
1408
|
accessControl,
|
|
1303
1409
|
creatorAccess,
|
|
1304
1410
|
changeGate,
|
|
1305
|
-
sessionOntologyService,
|
|
1306
1411
|
routineWritePolicy,
|
|
1307
1412
|
skillService,
|
|
1308
1413
|
pendingSkillsService,
|
|
@@ -1353,6 +1458,7 @@ export async function createCoreServices(
|
|
|
1353
1458
|
toolAuthMiddleware,
|
|
1354
1459
|
manualAuthMiddleware,
|
|
1355
1460
|
toolHandlerFactory,
|
|
1461
|
+
writeAccess,
|
|
1356
1462
|
// Server-time seams — defaults here; the enterprise overlay overwrites
|
|
1357
1463
|
// (or, for the array, pushes into) these after construction.
|
|
1358
1464
|
sessionSink: ports.sessionSink ?? new UuidSessionSink(),
|
package/src/core/lifecycle.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Server } from 'node:http';
|
|
2
2
|
import { closeDb, type Database } from '../modules/database/connection.js';
|
|
3
3
|
import type { AdvisoryLease } from '../modules/database/advisory-lock.js';
|
|
4
|
-
import {
|
|
4
|
+
import { bootMaySurvive, type KbStartupRunner } from '../modules/workspace/startup/kb-startup-runner.js';
|
|
5
5
|
import { noteBesideCheckout } from '../modules/workspace/startup/beside-checkout.js';
|
|
6
6
|
import { unregisterBevelSecretsVariableLoader } from '../modules/secrets-vault/secrets-variable-loader.js';
|
|
7
7
|
import type { WorkflowService } from '../modules/workflow/workflow.service.js';
|
|
@@ -164,7 +164,13 @@ export function holdCommitWorkerLease(
|
|
|
164
164
|
running = false;
|
|
165
165
|
wake?.();
|
|
166
166
|
await loop;
|
|
167
|
+
// The worker is asked to stop whether or not this process is running
|
|
168
|
+
// it: a worker that can be held still (`holdable`) may be in the
|
|
169
|
+
// middle of a hold that uses the database, and its stop is what
|
|
170
|
+
// waits for that. Stopping a worker that is not running is nothing.
|
|
171
|
+
const idle = !workerRunning && !stopping;
|
|
167
172
|
await stopWorker();
|
|
173
|
+
if (idle) await worker.stop().catch((err: unknown) => log(`commit worker stop failed: ${String(err)}`));
|
|
168
174
|
await lease.release();
|
|
169
175
|
})();
|
|
170
176
|
return stopped;
|
|
@@ -202,6 +208,80 @@ export function withStartupTask(
|
|
|
202
208
|
};
|
|
203
209
|
}
|
|
204
210
|
|
|
211
|
+
/** A worker the lease loop drives, which something else may hold still for a while. */
|
|
212
|
+
export interface HoldableWorker extends LeasedWorker {
|
|
213
|
+
/**
|
|
214
|
+
* Run `work` with the worker stopped, and start it again afterwards if the
|
|
215
|
+
* lease loop still wants it running. A hold waits for the commit in flight
|
|
216
|
+
* to finish, as a stop does. Holds may overlap; the worker starts again
|
|
217
|
+
* when the last one ends, whether `work` resolved or threw.
|
|
218
|
+
*/
|
|
219
|
+
whileHeld<T>(work: () => Promise<T>): Promise<T>;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* A worker that can be HELD STILL without the lease loop knowing.
|
|
224
|
+
*
|
|
225
|
+
* The lease loop decides whether this process is the one draining the commit
|
|
226
|
+
* queue; it starts the worker when the lease is taken and stops it when the
|
|
227
|
+
* lease is lost. Moving the deployment to another repository needs a second,
|
|
228
|
+
* independent reason for the worker not to run: while working copies are
|
|
229
|
+
* being set aside and cloned again, a queued commit must not be written into
|
|
230
|
+
* a directory that is being renamed away, nor into the fresh clone of a
|
|
231
|
+
* repository it was never meant for. Stopping the loop itself would release
|
|
232
|
+
* the lease, and another process would take the queue over mid-move.
|
|
233
|
+
*
|
|
234
|
+
* So the two reasons are kept apart. `wanted` is the lease loop's word,
|
|
235
|
+
* `holds` counts the moves in flight, and the worker runs exactly when it is
|
|
236
|
+
* wanted and nothing holds it.
|
|
237
|
+
*
|
|
238
|
+
* A stop waits for the holds in flight as well as for the worker. Shutdown
|
|
239
|
+
* awaits this stop (bounded by its own budget) before it ends the database
|
|
240
|
+
* pool, and a move still setting copies aside and cloning needs that pool:
|
|
241
|
+
* it is either finished, or known to have been cut.
|
|
242
|
+
*/
|
|
243
|
+
export function holdable(worker: LeasedWorker): HoldableWorker {
|
|
244
|
+
let wanted = false;
|
|
245
|
+
const inFlight = new Set<Promise<unknown>>();
|
|
246
|
+
/**
|
|
247
|
+
* ONE stop at a time, shared by everyone who asks. A worker's own stop is
|
|
248
|
+
* idempotent by answering a second caller at once, while the first is
|
|
249
|
+
* still waiting for the commit in flight; a second hold given that answer
|
|
250
|
+
* would set a working copy aside under that commit.
|
|
251
|
+
*/
|
|
252
|
+
let stopping: Promise<void> | null = null;
|
|
253
|
+
const stopWorker = (): Promise<void> => {
|
|
254
|
+
stopping ??= worker.stop().finally(() => {
|
|
255
|
+
stopping = null;
|
|
256
|
+
});
|
|
257
|
+
return stopping;
|
|
258
|
+
};
|
|
259
|
+
return {
|
|
260
|
+
start() {
|
|
261
|
+
wanted = true;
|
|
262
|
+
if (inFlight.size === 0) worker.start();
|
|
263
|
+
},
|
|
264
|
+
async stop() {
|
|
265
|
+
wanted = false;
|
|
266
|
+
await stopWorker();
|
|
267
|
+
await Promise.allSettled([...inFlight]);
|
|
268
|
+
},
|
|
269
|
+
whileHeld<T>(work: () => Promise<T>): Promise<T> {
|
|
270
|
+
const held = (async () => {
|
|
271
|
+
await stopWorker();
|
|
272
|
+
return work();
|
|
273
|
+
})();
|
|
274
|
+
inFlight.add(held);
|
|
275
|
+
const ended = (): void => {
|
|
276
|
+
inFlight.delete(held);
|
|
277
|
+
if (inFlight.size === 0 && wanted) worker.start();
|
|
278
|
+
};
|
|
279
|
+
held.then(ended, ended);
|
|
280
|
+
return held;
|
|
281
|
+
},
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
205
285
|
/**
|
|
206
286
|
* What one knowledge base's graph needs of itself to be STARTED: the boot
|
|
207
287
|
* side effects that used to run inside `createCoreServer`, named so a host
|
|
@@ -228,7 +308,8 @@ export interface BootableCore {
|
|
|
228
308
|
* jobs. `createCoreServer` runs this for a single-tenant deployment; a host
|
|
229
309
|
* serving several knowledge bases runs it when a tenant is activated.
|
|
230
310
|
*
|
|
231
|
-
* Throws to stop the boot on every failure but
|
|
311
|
+
* Throws to stop the boot on every failure but the ones the host answered
|
|
312
|
+
* about the repository or the credentials (see `bootMaySurvive`) — the
|
|
232
313
|
* container's restart policy is the retry — see `kb-startup-runner.ts`.
|
|
233
314
|
*/
|
|
234
315
|
export async function startCore<C extends BootableCore>(
|
|
@@ -267,18 +348,22 @@ export async function startCore<C extends BootableCore>(
|
|
|
267
348
|
try {
|
|
268
349
|
await core.kbStartupRunner.runAll();
|
|
269
350
|
} catch (err) {
|
|
270
|
-
// The
|
|
271
|
-
//
|
|
272
|
-
//
|
|
273
|
-
//
|
|
274
|
-
//
|
|
351
|
+
// The failures a boot survives: the host ANSWERED about the repository or
|
|
352
|
+
// the credentials, or could not be reached at all. None of them says
|
|
353
|
+
// anything about the knowledge base — what we would write is not known to
|
|
354
|
+
// be wrong — and every one of them is fixed somewhere else: the host comes
|
|
355
|
+
// back, the repository is created, the token is granted access, a rotated
|
|
356
|
+
// token is typed into the setup screen. Refusing to boot took away the
|
|
357
|
+
// login and setup screens the fix is entered on, and on 2026-09-28 a
|
|
358
|
+
// repository that had been replaced crash-looped a deployment for exactly
|
|
359
|
+
// that reason. The deployment comes up GATED instead: the setup routes
|
|
275
360
|
// read the runner's standing failure and keep the app shut, the setup
|
|
276
|
-
// screen shows why, saving it retries, and the runner keeps trying on
|
|
277
|
-
//
|
|
278
|
-
//
|
|
279
|
-
if (!(err
|
|
280
|
-
startupLog.error('booting UNMAINTAINED and gated — the knowledge
|
|
281
|
-
detail: err.message,
|
|
361
|
+
// screen shows why, saving it retries, and the runner keeps trying on its
|
|
362
|
+
// own. Every other failure still stops the boot, because it means the
|
|
363
|
+
// template or a step would write something wrong. See `bootMaySurvive`.
|
|
364
|
+
if (!bootMaySurvive(err)) throw err;
|
|
365
|
+
startupLog.error('booting UNMAINTAINED and gated — the knowledge base could not be initialized:', {
|
|
366
|
+
detail: err instanceof Error ? err.message : String(err),
|
|
282
367
|
});
|
|
283
368
|
// Kept on the graph so that stopping the graph stops the asking: a
|
|
284
369
|
// retry that outlived its graph would clone into a workspaces folder
|
package/src/core-config.ts
CHANGED
|
@@ -6,6 +6,8 @@ import { assertKeyDecodesTo32Bytes } from './shared/token-crypto.js';
|
|
|
6
6
|
import { DEFAULT_GIT_TIMEOUT_MS } from './modules/workflow/git/node-git-runner.js';
|
|
7
7
|
import { DEFAULT_DB_SCHEMA, assertSchemaName } from './modules/database/connection.js';
|
|
8
8
|
import { logger } from './shared/logging.js';
|
|
9
|
+
import { hasControlCharacter } from './modules/kb-fs/repo-path.js';
|
|
10
|
+
import { sanitizedPath } from './shared/printable.js';
|
|
9
11
|
|
|
10
12
|
const log = logger('config');
|
|
11
13
|
|
|
@@ -100,7 +102,6 @@ export interface TenantConfig {
|
|
|
100
102
|
readonly gitUsername: string;
|
|
101
103
|
readonly gitToken: string;
|
|
102
104
|
readonly kbTemplateDir: string;
|
|
103
|
-
readonly ontologySessionBlock: boolean;
|
|
104
105
|
readonly updateCheckEnabled: boolean;
|
|
105
106
|
readonly loginPasswordEnabled: boolean;
|
|
106
107
|
readonly allowedEmailDomains: string[];
|
|
@@ -259,17 +260,6 @@ export class CoreConfig implements TenantConfig, ProcessConfig {
|
|
|
259
260
|
* existing one. Override with `KB_TEMPLATE_DIR`.
|
|
260
261
|
*/
|
|
261
262
|
readonly kbTemplateDir: string;
|
|
262
|
-
/**
|
|
263
|
-
* Ontology-session boundary kill-switch. When true (default), an agent run
|
|
264
|
-
* that has read across more than one ontology can no longer write. A no-op on
|
|
265
|
-
* a single-ontology KB. Set `ONTOLOGY_SESSION_BLOCK=false` to disable.
|
|
266
|
-
*
|
|
267
|
-
* Stays on CORE config (not `AppConfig`) even though the write BLOCK itself
|
|
268
|
-
* is an enterprise-registered hook: the flag also switches off the core-owned
|
|
269
|
-
* touch TRACKING (the gate skips entirely when false — see
|
|
270
|
-
* `session-ontology.gate.ts`), so it must exist wherever the tracking runs.
|
|
271
|
-
*/
|
|
272
|
-
readonly ontologySessionBlock: boolean;
|
|
273
263
|
/**
|
|
274
264
|
* In-app update check. When true (default), `GET /api/update-check` lazily
|
|
275
265
|
* asks api.github.com for the newest Hexis release — only when an admin's
|
|
@@ -469,8 +459,6 @@ export class CoreConfig implements TenantConfig, ProcessConfig {
|
|
|
469
459
|
// Default: the `kb-template/` folder shipped inside this package (works
|
|
470
460
|
// both from src/ and compiled dist/ — see assets.ts).
|
|
471
461
|
this.kbTemplateDir = process.env.KB_TEMPLATE_DIR || defaultKbTemplateDir();
|
|
472
|
-
this.ontologySessionBlock =
|
|
473
|
-
(process.env.ONTOLOGY_SESSION_BLOCK ?? 'true').trim().toLowerCase() !== 'false';
|
|
474
462
|
this.updateCheckEnabled =
|
|
475
463
|
(process.env.UPDATE_CHECK ?? 'true').trim().toLowerCase() !== 'false';
|
|
476
464
|
this.allowedEmailDomains = (process.env.ALLOWED_EMAIL_DOMAINS || '')
|
|
@@ -621,5 +609,16 @@ export class CoreConfig implements TenantConfig, ProcessConfig {
|
|
|
621
609
|
) {
|
|
622
610
|
throw new Error(`KB_DIR_NAME must be a single path segment: ${this.kbDirName}`);
|
|
623
611
|
}
|
|
612
|
+
// A control character, a line break above all: this name is PREFIXED onto
|
|
613
|
+
// every workspace-relative path, and a line break in a path is a separator
|
|
614
|
+
// in git's line-oriented stdin protocols (`cat-file --batch` reads one
|
|
615
|
+
// `<ref>:<path>` per line). A name carrying one would make the normaliser
|
|
616
|
+
// emit the very path it refuses from a caller — so the deployment fails to
|
|
617
|
+
// boot rather than resolving paths nothing downstream can safely read.
|
|
618
|
+
if (hasControlCharacter(this.kbDirName)) {
|
|
619
|
+
throw new Error(
|
|
620
|
+
`KB_DIR_NAME must not contain a control character: "${sanitizedPath(this.kbDirName)}"`,
|
|
621
|
+
);
|
|
622
|
+
}
|
|
624
623
|
}
|
|
625
624
|
}
|
package/src/index.ts
CHANGED
|
@@ -101,8 +101,18 @@ export type {
|
|
|
101
101
|
export type { ILlmUsageMeter } from './modules/tool-auth/llm-usage-meter.js';
|
|
102
102
|
export type { AuthProviderPlugin } from './modules/auth/auth.routes.js';
|
|
103
103
|
export type { IErasureParticipant } from './modules/auth/account-erasure.service.js';
|
|
104
|
+
export {
|
|
105
|
+
alwaysWritable,
|
|
106
|
+
READ_ONLY_CODE,
|
|
107
|
+
type IWriteAccess,
|
|
108
|
+
type WriteAccessVerdict,
|
|
109
|
+
} from './modules/write-access/write-access.js';
|
|
104
110
|
export {
|
|
105
111
|
AccountAdmissionRefusedError,
|
|
112
|
+
AccountDeactivatedError,
|
|
113
|
+
AccountChangeRefusedError,
|
|
114
|
+
AuthBackendError,
|
|
115
|
+
ACCOUNT_DEACTIVATED_MESSAGE,
|
|
106
116
|
admitEveryone,
|
|
107
117
|
type AccountAdmissionVerdict,
|
|
108
118
|
type AccountProvisionReason,
|
|
@@ -112,6 +122,7 @@ export {
|
|
|
112
122
|
WorkflowHooks,
|
|
113
123
|
type CommitValidationHook,
|
|
114
124
|
type CommitValidationContext,
|
|
125
|
+
type AgentOperationContext,
|
|
126
|
+
type AgentReadHook,
|
|
115
127
|
type PreWriteHook,
|
|
116
|
-
type PreWriteContext,
|
|
117
128
|
} from './modules/workflow/workflow-hooks.js';
|