@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
|
@@ -99,6 +99,20 @@
|
|
|
99
99
|
"when": 1790243482866,
|
|
100
100
|
"tag": "0013_agent_identity_and_key_soft_delete",
|
|
101
101
|
"breakpoints": true
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
"idx": 14,
|
|
105
|
+
"version": "7",
|
|
106
|
+
"when": 1790667040382,
|
|
107
|
+
"tag": "0014_change_request_closed_reason",
|
|
108
|
+
"breakpoints": true
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"idx": 15,
|
|
112
|
+
"version": "7",
|
|
113
|
+
"when": 1790892582327,
|
|
114
|
+
"tag": "0015_account_deactivation",
|
|
115
|
+
"breakpoints": true
|
|
102
116
|
}
|
|
103
117
|
]
|
|
104
118
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bevel-software/platform-core-backend",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.0",
|
|
4
4
|
"description": "Open-source core backend of the Bevel platform: git-backed knowledge workspace, workflow (branches/change requests/locks/SSE), skills, tools, secrets vault, access control and the remote MCP surface.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -52,8 +52,8 @@
|
|
|
52
52
|
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz",
|
|
53
53
|
"yaml": "^2.9.0",
|
|
54
54
|
"zod": "^3.24.0",
|
|
55
|
-
"@bevel-software/platform-
|
|
56
|
-
"@bevel-software/platform-
|
|
55
|
+
"@bevel-software/platform-shared": "0.23.0",
|
|
56
|
+
"@bevel-software/platform-mcp-core": "0.23.0"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
59
|
"@types/adm-zip": "^0.5.8",
|
|
@@ -10,7 +10,6 @@ import {
|
|
|
10
10
|
currentKbLayout,
|
|
11
11
|
isPlatformFile,
|
|
12
12
|
mentionsAgentsFile,
|
|
13
|
-
ontologyRoots,
|
|
14
13
|
platformFileNames,
|
|
15
14
|
pluginOfPath,
|
|
16
15
|
renderKbLayoutPlaceholders,
|
|
@@ -105,7 +104,6 @@ describe('KB layout — configuration', () => {
|
|
|
105
104
|
|
|
106
105
|
test('the derived sets follow the configured names rather than snapshotting the defaults', () => {
|
|
107
106
|
configureKbLayout({ knowledgeBaseDir: 'docs', skillsDir: 'skills', pluginsDir: 'plugins' });
|
|
108
|
-
expect(ontologyRoots(currentKbLayout())).toEqual(['docs', 'Data']);
|
|
109
107
|
expect(reservedRootDirNames(currentKbLayout()).has('plugins')).toBe(true);
|
|
110
108
|
expect(reservedRootDirNames(currentKbLayout()).has('Plugins')).toBe(false);
|
|
111
109
|
// Path rules read the configured name too.
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { readFile, readdir } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { CoreConfig } from '../core-config.js';
|
|
6
|
+
import { PLATFORM_HEADER, TOOL_PREFIX_LINE } from '../modules/agent-instructions/compose.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* What a deployment is left holding after the per-conversation boundary moved
|
|
10
|
+
* out of these packages: a variable nobody reads, a table nobody touches, and
|
|
11
|
+
* no wording that names what Hexis no longer knows about.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const REQUIRED = {
|
|
15
|
+
KB_REPO_URL: 'https://example.com/org/kb.git',
|
|
16
|
+
ADMIN_EMAIL: 'root@example.com',
|
|
17
|
+
ADMIN_PASSWORD: 'sup3r-secret',
|
|
18
|
+
JWT_SECRET: 'test-jwt-secret',
|
|
19
|
+
SECRETS_ENC_KEY: 'kToAi8FXWDpDn3A6yQ/60O39bv05N7XzVOIu/0CJrFc=',
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
let saved: NodeJS.ProcessEnv;
|
|
23
|
+
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
saved = { ...process.env };
|
|
26
|
+
delete process.env.ONTOLOGY_SESSION_BLOCK;
|
|
27
|
+
Object.assign(process.env, REQUIRED);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
afterEach(() => {
|
|
31
|
+
process.env = saved;
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe('ONTOLOGY_SESSION_BLOCK is retired', () => {
|
|
35
|
+
it('is ignored: the config builds either way, with no such setting on it', () => {
|
|
36
|
+
for (const value of ['false', 'true', 'nonsense']) {
|
|
37
|
+
process.env.ONTOLOGY_SESSION_BLOCK = value;
|
|
38
|
+
const config = new CoreConfig();
|
|
39
|
+
expect(config.kbRepoUrl).toBe(REQUIRED.KB_REPO_URL);
|
|
40
|
+
expect(config).not.toHaveProperty('ontologySessionBlock');
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('a config built with it set is the same as one built without it', () => {
|
|
45
|
+
const without = JSON.stringify(new CoreConfig());
|
|
46
|
+
process.env.ONTOLOGY_SESSION_BLOCK = 'false';
|
|
47
|
+
expect(JSON.stringify(new CoreConfig())).toBe(without);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// BOTH reference docs, so neither can be left promising what the other
|
|
51
|
+
// retired: `.env.example` is the one a deployment COPIES, so an entry
|
|
52
|
+
// surviving there is an inert kill-switch somebody sets and believes in.
|
|
53
|
+
it.each(['docs/configuration.md', '.env.example'])('%s documents no variable named for it', async (doc) => {
|
|
54
|
+
const here = fileURLToPath(new URL('.', import.meta.url));
|
|
55
|
+
const text = await readFile(join(here, '../../../../', doc), 'utf8');
|
|
56
|
+
expect(text).not.toContain('ONTOLOGY_SESSION_BLOCK');
|
|
57
|
+
expect(text.toLowerCase()).not.toContain('ontolog');
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe('what an agent or a person reads names no ontology', () => {
|
|
62
|
+
it('the server instructions do not', () => {
|
|
63
|
+
expect(PLATFORM_HEADER.toLowerCase()).not.toContain('ontolog');
|
|
64
|
+
expect(TOOL_PREFIX_LINE.toLowerCase()).not.toContain('ontolog');
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* The table stays in this release so that no deployment runs a version in
|
|
70
|
+
* which neither side has it — but nothing in this package may read or write it
|
|
71
|
+
* any more. The schema file that declares it is the one and only mention.
|
|
72
|
+
*/
|
|
73
|
+
describe('session_ontology_touches is unused', () => {
|
|
74
|
+
it('is named by the schema that declares it and by nothing else', async () => {
|
|
75
|
+
const src = fileURLToPath(new URL('..', import.meta.url));
|
|
76
|
+
const self = fileURLToPath(import.meta.url);
|
|
77
|
+
const found: string[] = [];
|
|
78
|
+
const walk = async (dir: string): Promise<void> => {
|
|
79
|
+
for (const entry of await readdir(dir, { withFileTypes: true })) {
|
|
80
|
+
const path = join(dir, entry.name);
|
|
81
|
+
if (entry.isDirectory()) {
|
|
82
|
+
await walk(path);
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
// This file names the table to look for it; it is not a use of it.
|
|
86
|
+
if (!entry.name.endsWith('.ts') || path === self) continue;
|
|
87
|
+
const text = await readFile(path, 'utf8');
|
|
88
|
+
if (text.includes('sessionOntologyTouches') || text.includes('session_ontology_touches')) {
|
|
89
|
+
found.push(path.slice(src.length));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
await walk(src);
|
|
94
|
+
expect(found).toEqual(['modules/database/core-schema.ts']);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { describe, it, expect, vi, afterEach, beforeEach } from 'vitest';
|
|
2
|
+
import fsp from 'node:fs/promises';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { startCore, type BootableCore } from '../lifecycle.js';
|
|
6
|
+
import { ClassifiedFailure, gitFailure } from '../../shared/git-failure.js';
|
|
7
|
+
import {
|
|
8
|
+
KbRemoteUnreachableError,
|
|
9
|
+
bootMaySurvive,
|
|
10
|
+
} from '../../modules/workspace/startup/kb-startup-runner.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A boot over a knowledge-base repository the host will not serve.
|
|
14
|
+
*
|
|
15
|
+
* On 2026-09-28 a deployment's repository was deleted and replaced. The new
|
|
16
|
+
* address saved and its connection test passed, but a working copy still
|
|
17
|
+
* fetching through the OLD address answered "repository not found" inside a
|
|
18
|
+
* startup step — which was not the one failure a boot survived, so the
|
|
19
|
+
* container stopped, restarted, and stopped again, taking the setup screen
|
|
20
|
+
* that would have fixed it with it.
|
|
21
|
+
*
|
|
22
|
+
* The rule now: whatever the host ANSWERED about the repository or the
|
|
23
|
+
* credentials lets the boot come up GATED. Only a failure that says the
|
|
24
|
+
* knowledge base itself would be written wrong still stops it.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/** An empty workspaces root, so the beside-the-checkout note finds nothing to say. */
|
|
28
|
+
let workspacesRoot: string;
|
|
29
|
+
|
|
30
|
+
beforeEach(async () => {
|
|
31
|
+
workspacesRoot = await fsp.mkdtemp(path.join(os.tmpdir(), 'gated-boot-'));
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
afterEach(async () => {
|
|
35
|
+
await fsp.rm(workspacesRoot, { recursive: true, force: true });
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
/** A core whose startup phase fails in a way the test picks. */
|
|
39
|
+
function bootable(runAll: () => Promise<void>) {
|
|
40
|
+
const retries = { started: 0 };
|
|
41
|
+
const core: BootableCore = {
|
|
42
|
+
config: { workspacesRoot },
|
|
43
|
+
kbDirName: 'knowledge-base',
|
|
44
|
+
kbStartupRunner: {
|
|
45
|
+
runAll,
|
|
46
|
+
retryUntilMaintained: () => {
|
|
47
|
+
retries.started += 1;
|
|
48
|
+
return { stop() {} };
|
|
49
|
+
},
|
|
50
|
+
} as BootableCore['kbStartupRunner'],
|
|
51
|
+
workflowService: {
|
|
52
|
+
closeChangeRequestsWithDeletedBranches: async () => 0,
|
|
53
|
+
} as BootableCore['workflowService'],
|
|
54
|
+
pluginJoinRequestJobs: { startSweeping() {} } as BootableCore['pluginJoinRequestJobs'],
|
|
55
|
+
startupRetry: null,
|
|
56
|
+
};
|
|
57
|
+
return { core, retries };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
afterEach(() => {
|
|
61
|
+
vi.restoreAllMocks();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe('startCore over a repository the host will not serve', () => {
|
|
65
|
+
it('comes up gated on "repository not found" raised inside the phase — no restart loop', async () => {
|
|
66
|
+
vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
67
|
+
const { core, retries } = bootable(async () => {
|
|
68
|
+
throw new ClassifiedFailure(
|
|
69
|
+
'KB startup step "groups-to-plugins" failed: git fetch failed: repository not found',
|
|
70
|
+
gitFailure('not-found'),
|
|
71
|
+
);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Resolves: the server boots. The setup routes keep it gated on the
|
|
75
|
+
// runner's standing failure, and the admin can sign in and fix it.
|
|
76
|
+
await expect(startCore(core)).resolves.toBeUndefined();
|
|
77
|
+
// And it keeps asking on its own: a repository that is created, or a token
|
|
78
|
+
// that is granted access to it, needs no settings change on this side.
|
|
79
|
+
expect(retries.started).toBe(1);
|
|
80
|
+
expect(core.startupRetry).not.toBeNull();
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('comes up gated when the host rejected the credentials', async () => {
|
|
84
|
+
vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
85
|
+
const { core } = bootable(async () => {
|
|
86
|
+
throw new ClassifiedFailure('git push failed: Authentication failed', gitFailure('credentials-rejected'));
|
|
87
|
+
});
|
|
88
|
+
await expect(startCore(core)).resolves.toBeUndefined();
|
|
89
|
+
// The same gated contract as the sibling above: the boot hands the retry
|
|
90
|
+
// handle to the runner rather than leaving the deployment to a restart.
|
|
91
|
+
// (Handle CREATION is what this pins — the stub counts
|
|
92
|
+
// `retryUntilMaintained` calls, which cannot tell a handle being made from
|
|
93
|
+
// a retry loop actually running; that semantics is
|
|
94
|
+
// `kb-startup-runner.degraded.test.ts`'s.)
|
|
95
|
+
expect(core.startupRetry).not.toBeNull();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('still STOPS the boot when the failure is about what a step would write', async () => {
|
|
99
|
+
const { core, retries } = bootable(async () => {
|
|
100
|
+
throw new ClassifiedFailure(
|
|
101
|
+
'KB startup step "template-files" failed: the template is missing roles.yaml',
|
|
102
|
+
{ kind: 'step-failed', cause: 'The startup step "template-files" failed.' },
|
|
103
|
+
);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// Booting over this would scaffold something wrong into every branch.
|
|
107
|
+
await expect(startCore(core)).rejects.toThrow(/template-files/);
|
|
108
|
+
expect(retries.started).toBe(0);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
describe('bootMaySurvive', () => {
|
|
113
|
+
it('says yes to the remote being out of reach, whatever raised it', () => {
|
|
114
|
+
expect(bootMaySurvive(new KbRemoteUnreachableError('could not be reached'))).toBe(true);
|
|
115
|
+
expect(bootMaySurvive(new ClassifiedFailure('x', gitFailure('unreachable')))).toBe(true);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('says yes to what the host answered about the repository or the credentials', () => {
|
|
119
|
+
for (const kind of ['not-found', 'credentials-rejected', 'write-refused'] as const) {
|
|
120
|
+
expect({ kind, survives: bootMaySurvive(new ClassifiedFailure('x', gitFailure(kind))) }).toEqual({
|
|
121
|
+
kind,
|
|
122
|
+
survives: true,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('says no to everything else — those say the knowledge base would be written wrong', () => {
|
|
128
|
+
expect(bootMaySurvive(new ClassifiedFailure('x', gitFailure('push-refused-by-policy')))).toBe(false);
|
|
129
|
+
expect(bootMaySurvive(new ClassifiedFailure('x', gitFailure('unknown')))).toBe(false);
|
|
130
|
+
expect(bootMaySurvive(new Error('a step threw'))).toBe(false);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { createShutdown, holdCommitWorkerLease, stopCore, withStartupTask, type LeasedWorker } from '../lifecycle.js';
|
|
2
|
+
import { createShutdown, holdCommitWorkerLease, holdable, stopCore, withStartupTask, type LeasedWorker } from '../lifecycle.js';
|
|
3
3
|
import { BevelSecretsVariableLoader, registerBevelSecretsVariableLoader } from '../../modules/secrets-vault/secrets-variable-loader.js';
|
|
4
4
|
import type { AdvisoryLease } from '../../modules/database/advisory-lock.js';
|
|
5
5
|
|
|
@@ -158,6 +158,31 @@ describe('holdCommitWorkerLease', () => {
|
|
|
158
158
|
await handle.stop();
|
|
159
159
|
expect(record.released).toBe(1);
|
|
160
160
|
});
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* A process that never held the lease never ran the worker, and can still
|
|
164
|
+
* be in the middle of a hold on it: a repository move, using the database.
|
|
165
|
+
* Shutdown ends the pool after this stop, so this stop is what waits.
|
|
166
|
+
*/
|
|
167
|
+
it('stop() waits for a hold in flight on a worker this process never ran', async () => {
|
|
168
|
+
const { lease, record } = fakeLease([false]);
|
|
169
|
+
const worker = holdable({ start: () => undefined, stop: async () => undefined });
|
|
170
|
+
const clock = manualSleep();
|
|
171
|
+
const handle = holdCommitWorkerLease(lease, worker, { sleep: clock.sleep, log: () => undefined });
|
|
172
|
+
await settle();
|
|
173
|
+
|
|
174
|
+
let finishMove: () => void = () => undefined;
|
|
175
|
+
const move = worker.whileHeld(() => new Promise<void>((resolve) => (finishMove = resolve)));
|
|
176
|
+
await settle();
|
|
177
|
+
const stopping = handle.stop();
|
|
178
|
+
await settle();
|
|
179
|
+
expect(record.released).toBe(0);
|
|
180
|
+
|
|
181
|
+
finishMove();
|
|
182
|
+
await move;
|
|
183
|
+
await stopping;
|
|
184
|
+
expect(record.released).toBe(1);
|
|
185
|
+
});
|
|
161
186
|
});
|
|
162
187
|
|
|
163
188
|
describe('withStartupTask', () => {
|
|
@@ -444,3 +469,147 @@ describe('stopCore', () => {
|
|
|
444
469
|
expect(g.order).toEqual(['backgroundJobs.stopSweeping', 'startupRetry.stop', 'backgroundJobs.drain', 'db.end']);
|
|
445
470
|
});
|
|
446
471
|
});
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Two independent reasons for the commit worker not to run: the lease loop
|
|
475
|
+
* does not want it (another process holds the queue), or something holds it
|
|
476
|
+
* still (the deployment is being moved to another repository). It runs
|
|
477
|
+
* exactly when it is wanted and nothing holds it.
|
|
478
|
+
*/
|
|
479
|
+
describe('holdable', () => {
|
|
480
|
+
function recorded() {
|
|
481
|
+
const events: string[] = [];
|
|
482
|
+
const worker: LeasedWorker = {
|
|
483
|
+
start: () => void events.push('start'),
|
|
484
|
+
stop: async () => void events.push('stop'),
|
|
485
|
+
};
|
|
486
|
+
return { events, worker: holdable(worker) };
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
it('stops a running worker for the work, and starts it again afterwards', async () => {
|
|
490
|
+
const { events, worker } = recorded();
|
|
491
|
+
worker.start();
|
|
492
|
+
const answer = await worker.whileHeld(async () => {
|
|
493
|
+
events.push('work');
|
|
494
|
+
return 42;
|
|
495
|
+
});
|
|
496
|
+
expect(answer).toBe(42);
|
|
497
|
+
expect(events).toEqual(['start', 'stop', 'work', 'start']);
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
it('starts it again when the work fails, and hands the failure on', async () => {
|
|
501
|
+
const { events, worker } = recorded();
|
|
502
|
+
worker.start();
|
|
503
|
+
await expect(
|
|
504
|
+
worker.whileHeld(async () => {
|
|
505
|
+
throw new Error('the move failed');
|
|
506
|
+
}),
|
|
507
|
+
).rejects.toThrow('the move failed');
|
|
508
|
+
expect(events).toEqual(['start', 'stop', 'start']);
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
it('does not start a worker the lease loop never wanted', async () => {
|
|
512
|
+
const { events, worker } = recorded();
|
|
513
|
+
await worker.whileHeld(async () => undefined);
|
|
514
|
+
expect(events).not.toContain('start');
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
it('holds back a start the lease loop asks for during the work, and makes it afterwards', async () => {
|
|
518
|
+
const { events, worker } = recorded();
|
|
519
|
+
await worker.whileHeld(async () => {
|
|
520
|
+
// The lease is taken while the move runs.
|
|
521
|
+
worker.start();
|
|
522
|
+
events.push('work');
|
|
523
|
+
});
|
|
524
|
+
expect(events).toEqual(['stop', 'work', 'start']);
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
it('leaves it stopped when the lease loop stopped it during the work', async () => {
|
|
528
|
+
const { events, worker } = recorded();
|
|
529
|
+
worker.start();
|
|
530
|
+
// The loop's stop waits for the hold, so it is not awaited from inside it.
|
|
531
|
+
let stopping: Promise<void> = Promise.resolve();
|
|
532
|
+
await worker.whileHeld(async () => {
|
|
533
|
+
stopping = worker.stop();
|
|
534
|
+
await settle();
|
|
535
|
+
});
|
|
536
|
+
await stopping;
|
|
537
|
+
expect(events.at(-1)).toBe('stop');
|
|
538
|
+
expect(events.filter((e) => e === 'start')).toHaveLength(1);
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* Shutdown awaits this stop before it ends the database pool. A move still
|
|
543
|
+
* setting copies aside needs that pool, so the stop does not come back
|
|
544
|
+
* while a hold is in flight.
|
|
545
|
+
*/
|
|
546
|
+
it('does not finish stopping while a hold is still in flight', async () => {
|
|
547
|
+
const { worker } = recorded();
|
|
548
|
+
worker.start();
|
|
549
|
+
let finishMove: () => void = () => undefined;
|
|
550
|
+
const move = worker.whileHeld(() => new Promise<void>((resolve) => (finishMove = resolve)));
|
|
551
|
+
await settle();
|
|
552
|
+
let stopped = false;
|
|
553
|
+
const stopping = worker.stop().then(() => (stopped = true));
|
|
554
|
+
await settle();
|
|
555
|
+
expect(stopped).toBe(false);
|
|
556
|
+
finishMove();
|
|
557
|
+
await move;
|
|
558
|
+
await stopping;
|
|
559
|
+
expect(stopped).toBe(true);
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
it('finishes stopping when the hold in flight fails', async () => {
|
|
563
|
+
const { worker } = recorded();
|
|
564
|
+
let failMove: (err: Error) => void = () => undefined;
|
|
565
|
+
const move = worker.whileHeld(() => new Promise<void>((_, reject) => (failMove = reject)));
|
|
566
|
+
move.catch(() => undefined);
|
|
567
|
+
await settle();
|
|
568
|
+
const stopping = worker.stop();
|
|
569
|
+
failMove(new Error('the move failed'));
|
|
570
|
+
await expect(stopping).resolves.toBeUndefined();
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* A worker's stop answers a second caller at once while the first is
|
|
575
|
+
* still waiting for the commit in flight. A second hold given that answer
|
|
576
|
+
* would set a working copy aside under that commit.
|
|
577
|
+
*/
|
|
578
|
+
it('makes a second hold wait for the stop the first one is still waiting on', async () => {
|
|
579
|
+
const events: string[] = [];
|
|
580
|
+
let stopped = false;
|
|
581
|
+
let finishStop: () => void = () => undefined;
|
|
582
|
+
const worker = holdable({
|
|
583
|
+
start: () => void events.push('start'),
|
|
584
|
+
// As the commit worker's: the first call waits, a later one returns.
|
|
585
|
+
stop: () => {
|
|
586
|
+
if (stopped) return Promise.resolve();
|
|
587
|
+
stopped = true;
|
|
588
|
+
return new Promise<void>((resolve) => (finishStop = resolve));
|
|
589
|
+
},
|
|
590
|
+
});
|
|
591
|
+
worker.start();
|
|
592
|
+
const first = worker.whileHeld(async () => void events.push('first work'));
|
|
593
|
+
const second = worker.whileHeld(async () => void events.push('second work'));
|
|
594
|
+
await settle();
|
|
595
|
+
// The commit in flight has not finished: neither hold has begun.
|
|
596
|
+
expect(events).toEqual(['start']);
|
|
597
|
+
finishStop();
|
|
598
|
+
await Promise.all([first, second]);
|
|
599
|
+
expect(events.slice(0, 3).sort()).toEqual(['first work', 'second work', 'start']);
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
it('waits for the last of two overlapping holds before it starts again', async () => {
|
|
603
|
+
const { events, worker } = recorded();
|
|
604
|
+
worker.start();
|
|
605
|
+
let finishFirst: () => void = () => undefined;
|
|
606
|
+
const first = worker.whileHeld(() => new Promise<void>((resolve) => (finishFirst = resolve)));
|
|
607
|
+
await settle();
|
|
608
|
+
await worker.whileHeld(async () => undefined);
|
|
609
|
+
// The second hold ended and the first is still running: not started yet.
|
|
610
|
+
expect(events.filter((e) => e === 'start')).toHaveLength(1);
|
|
611
|
+
finishFirst();
|
|
612
|
+
await first;
|
|
613
|
+
expect(events.filter((e) => e === 'start')).toHaveLength(2);
|
|
614
|
+
});
|
|
615
|
+
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { setAsideRootFor } from '../../modules/workspace/set-aside-clone.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A working copy of a repository that is no longer the configured one is SET
|
|
9
|
+
* ASIDE rather than deleted, and two places do it: the KB startup phase,
|
|
10
|
+
* sweeping the whole workspaces root, and the workspace service, refusing to
|
|
11
|
+
* adopt one on a branch open. They must set aside into ONE directory.
|
|
12
|
+
*
|
|
13
|
+
* They did not. The composition root gave the phase a root under the backups
|
|
14
|
+
* volume and the workspace service nothing at all, so that one fell back to a
|
|
15
|
+
* sibling of the workspaces root — which in the shipped compose file is the
|
|
16
|
+
* container's own filesystem, not a volume. A copy the workspace service kept
|
|
17
|
+
* was therefore gone at the next ordinary recreate, after the setup screen had
|
|
18
|
+
* told the admin it was being kept and where to find it.
|
|
19
|
+
*
|
|
20
|
+
* Read off the source, like `migrations-unqualified.test.ts` reads the
|
|
21
|
+
* migrations: the wiring is a single call graph that cannot be built in a unit
|
|
22
|
+
* test (it wants a database, a git host and a disk), and the invariant is
|
|
23
|
+
* about the TEXT — that the folder is named once and that one name reaches
|
|
24
|
+
* both. A second literal is exactly how the two came apart.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
const compositionRoot = path.resolve(
|
|
28
|
+
path.dirname(fileURLToPath(import.meta.url)),
|
|
29
|
+
'../create-core-services.ts',
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
describe('the set-aside root the composition root wires', () => {
|
|
33
|
+
it('is named once, and handed to both the startup phase and the workspace service', async () => {
|
|
34
|
+
const source = await fs.readFile(compositionRoot, 'utf8');
|
|
35
|
+
|
|
36
|
+
// Named once. Two occurrences means two expressions, which is how a
|
|
37
|
+
// change to one of them silently stops applying to the other.
|
|
38
|
+
const literals = source.match(/'replaced-working-copies'/g) ?? [];
|
|
39
|
+
expect(literals).toHaveLength(1);
|
|
40
|
+
|
|
41
|
+
// And that one name is rooted at the backups volume, which the deployment
|
|
42
|
+
// mounts — not at the workspaces root's parent, which it does not.
|
|
43
|
+
expect(source).toMatch(
|
|
44
|
+
/const replacedWorkingCopiesRoot = path\.join\(config\.backupsRoot, 'replaced-working-copies'\)/,
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
// Both consumers read the one variable.
|
|
48
|
+
expect(source).toMatch(/new WorkspaceService\([\s\S]*?replacedWorkingCopiesRoot,[\s\S]*?\);/);
|
|
49
|
+
expect(source).toMatch(/setAsideRoot: replacedWorkingCopiesRoot,/);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe('setAsideRootFor', () => {
|
|
54
|
+
it('uses the root it is given, whatever the workspaces root is', () => {
|
|
55
|
+
expect(setAsideRootFor('/app/apps/server/workspaces', '/app/apps/server/backups/replaced-working-copies')).toBe(
|
|
56
|
+
'/app/apps/server/backups/replaced-working-copies',
|
|
57
|
+
);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('falls back beside the workspaces root — a place a container deployment must not rely on', () => {
|
|
61
|
+
expect(setAsideRootFor('/app/apps/server/workspaces')).toBe('/app/apps/server/replaced-working-copies');
|
|
62
|
+
});
|
|
63
|
+
});
|
package/src/core/core-ports.ts
CHANGED
|
@@ -7,6 +7,7 @@ import type { ILlmUsageMeter } from '../modules/tool-auth/llm-usage-meter.js';
|
|
|
7
7
|
import type { AuthProviderPlugin } from '../modules/auth/auth.routes.js';
|
|
8
8
|
import type { IErasureParticipant } from '../modules/auth/account-erasure.service.js';
|
|
9
9
|
import type { IAccountAdmission } from '../modules/auth/account-admission.js';
|
|
10
|
+
import type { IWriteAccess } from '../modules/write-access/write-access.js';
|
|
10
11
|
import type { OnServerStart } from '../modules/workspace/startup/on-server-start.js';
|
|
11
12
|
|
|
12
13
|
/**
|
|
@@ -34,7 +35,7 @@ import type { OnServerStart } from '../modules/workspace/startup/on-server-start
|
|
|
34
35
|
* enterprise root overwrites the `CoreServices` fields (or pushes into the
|
|
35
36
|
* `authProviders` array) after construction, before the server is built.
|
|
36
37
|
*
|
|
37
|
-
* (Commit-time KB validation and the
|
|
38
|
+
* (Commit-time KB validation and the agent read/write hooks are NOT ports:
|
|
38
39
|
* they are workflow lifecycle HOOKS the enterprise root registers on
|
|
39
40
|
* `workflowService.hooks` after construction — see
|
|
40
41
|
* `modules/workflow/workflow-hooks.ts`.)
|
|
@@ -117,6 +118,15 @@ export interface CorePorts {
|
|
|
117
118
|
* admin who asked. See `modules/auth/account-admission.ts`.
|
|
118
119
|
*/
|
|
119
120
|
accountAdmission?: IAccountAdmission;
|
|
121
|
+
/**
|
|
122
|
+
* Whether the deployment may be changed right now. Asked before every
|
|
123
|
+
* mutating route (bar the ones that sign people in, manage accounts or
|
|
124
|
+
* configure the deployment) and every write tool. Core default: always.
|
|
125
|
+
* A host that sells seats answers no while more accounts are on than its
|
|
126
|
+
* plan allows, so the workspace is read-only until an admin fixes it. See
|
|
127
|
+
* `modules/write-access/write-access.ts`.
|
|
128
|
+
*/
|
|
129
|
+
writeAccess?: IWriteAccess;
|
|
120
130
|
}
|
|
121
131
|
|
|
122
132
|
/**
|