@bevel-software/platform-core-backend 0.35.0 → 0.37.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agent-guide/directory-structure.md +1 -1
- package/agent-guide/onboarding.md +111 -0
- package/agent-guide/start-here.md +1 -0
- package/agent-guide/tool-manuals.md +1 -0
- package/dist/core/catalog-cache-invalidation.d.ts +2 -2
- package/dist/core/catalog-cache-invalidation.d.ts.map +1 -1
- package/dist/core/compose-embed.d.ts +2 -2
- package/dist/core/compose-embed.d.ts.map +1 -1
- package/dist/core/compose-embed.js +1 -1
- package/dist/core/compose-embed.js.map +1 -1
- package/dist/core/core-ports.d.ts +19 -4
- package/dist/core/core-ports.d.ts.map +1 -1
- package/dist/core/core-ports.js.map +1 -1
- package/dist/core/create-core-server.d.ts.map +1 -1
- package/dist/core/create-core-server.js +26 -24
- package/dist/core/create-core-server.js.map +1 -1
- package/dist/core/create-core-services.d.ts +19 -13
- package/dist/core/create-core-services.d.ts.map +1 -1
- package/dist/core/create-core-services.js +72 -57
- package/dist/core/create-core-services.js.map +1 -1
- package/dist/core/lifecycle.d.ts +4 -4
- package/dist/core/lifecycle.d.ts.map +1 -1
- package/dist/core/lifecycle.js +5 -5
- package/dist/core/lifecycle.js.map +1 -1
- package/dist/core-config.d.ts +2 -2
- package/dist/core-config.js +3 -3
- package/dist/core-config.js.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/modules/access/access-control.service.js +1 -1
- package/dist/modules/access/access-control.service.js.map +1 -1
- package/dist/modules/access/access-declarations.d.ts +2 -2
- package/dist/modules/access/access-declarations.d.ts.map +1 -1
- package/dist/modules/access/access-declarations.js +2 -2
- package/dist/modules/access/access-declarations.js.map +1 -1
- package/dist/modules/access/access-mutation.service.d.ts +2 -2
- package/dist/modules/access/access-mutation.service.d.ts.map +1 -1
- package/dist/modules/access/access-mutation.service.js +2 -2
- package/dist/modules/access/access-mutation.service.js.map +1 -1
- package/dist/modules/access/access-requests.routes.d.ts +2 -2
- package/dist/modules/access/access-requests.routes.d.ts.map +1 -1
- package/dist/modules/access/access-requests.routes.js +5 -5
- package/dist/modules/access/access-requests.routes.js.map +1 -1
- package/dist/modules/access/access-requests.service.d.ts +2 -2
- package/dist/modules/access/access-requests.service.d.ts.map +1 -1
- package/dist/modules/access/access-requests.service.js +11 -11
- package/dist/modules/access/access-requests.service.js.map +1 -1
- package/dist/modules/access/access.routes.d.ts +2 -2
- package/dist/modules/access/access.routes.d.ts.map +1 -1
- package/dist/modules/access/access.routes.js +9 -9
- package/dist/modules/access/access.routes.js.map +1 -1
- package/dist/modules/access/admin-locked-commit.d.ts +5 -5
- package/dist/modules/access/admin-locked-commit.d.ts.map +1 -1
- package/dist/modules/access/admin-locked-commit.js +7 -7
- package/dist/modules/access/admin-locked-commit.js.map +1 -1
- package/dist/modules/access/admin-route-helpers.d.ts +1 -1
- package/dist/modules/access/admin-route-helpers.js +5 -5
- package/dist/modules/access/admin-route-helpers.js.map +1 -1
- package/dist/modules/access/change-read-gate.d.ts +1 -1
- package/dist/modules/access/change-read-gate.js +1 -1
- package/dist/modules/access/groups-admin.service.d.ts +7 -7
- package/dist/modules/access/groups-admin.service.d.ts.map +1 -1
- package/dist/modules/access/groups-admin.service.js +6 -6
- package/dist/modules/access/groups-admin.service.js.map +1 -1
- package/dist/modules/access/reference-scan.d.ts +1 -1
- package/dist/modules/access/roles-admin.service.d.ts +7 -7
- package/dist/modules/access/roles-admin.service.d.ts.map +1 -1
- package/dist/modules/access/roles-admin.service.js +8 -8
- package/dist/modules/access/roles-admin.service.js.map +1 -1
- package/dist/modules/access/synced-groups-committer.d.ts +4 -4
- package/dist/modules/access/synced-groups-committer.d.ts.map +1 -1
- package/dist/modules/access/synced-groups-committer.js +7 -7
- package/dist/modules/access/synced-groups-committer.js.map +1 -1
- package/dist/modules/access/user-access-removal.service.d.ts +6 -6
- package/dist/modules/access/user-access-removal.service.d.ts.map +1 -1
- package/dist/modules/access/user-access-removal.service.js +4 -4
- package/dist/modules/access/user-access-removal.service.js.map +1 -1
- package/dist/modules/access-model/access-errors.d.ts +4 -4
- package/dist/modules/access-model/access-errors.d.ts.map +1 -1
- package/dist/modules/access-model/access-errors.js +4 -4
- package/dist/modules/access-model/access-errors.js.map +1 -1
- package/dist/modules/access-model/access-grammar.d.ts +1 -1
- package/dist/modules/access-model/access-grammar.js +4 -4
- package/dist/modules/access-model/change-gate.d.ts +1 -1
- package/dist/modules/access-model/change-gate.js +1 -1
- package/dist/modules/access-model/roles-yaml-guard.d.ts +4 -4
- package/dist/modules/access-model/roles-yaml-guard.d.ts.map +1 -1
- package/dist/modules/access-model/roles-yaml-guard.js +4 -4
- package/dist/modules/access-model/roles-yaml-guard.js.map +1 -1
- package/dist/modules/agent-guide/agent-guide.d.ts +19 -14
- package/dist/modules/agent-guide/agent-guide.d.ts.map +1 -1
- package/dist/modules/agent-guide/agent-guide.js +42 -18
- package/dist/modules/agent-guide/agent-guide.js.map +1 -1
- package/dist/modules/agent-guide/guide-budget.d.ts +5 -2
- package/dist/modules/agent-guide/guide-budget.d.ts.map +1 -1
- package/dist/modules/agent-guide/guide-budget.js +6 -2
- package/dist/modules/agent-guide/guide-budget.js.map +1 -1
- package/dist/modules/agent-instructions/agent-instructions.routes.d.ts +12 -5
- package/dist/modules/agent-instructions/agent-instructions.routes.d.ts.map +1 -1
- package/dist/modules/agent-instructions/agent-instructions.routes.js +13 -6
- package/dist/modules/agent-instructions/agent-instructions.routes.js.map +1 -1
- package/dist/modules/agent-instructions/compose.d.ts +15 -8
- package/dist/modules/agent-instructions/compose.d.ts.map +1 -1
- package/dist/modules/agent-instructions/compose.js +24 -10
- package/dist/modules/agent-instructions/compose.js.map +1 -1
- package/dist/modules/agent-instructions/shared-file-rules.d.ts +14 -6
- package/dist/modules/agent-instructions/shared-file-rules.d.ts.map +1 -1
- package/dist/modules/agent-instructions/shared-file-rules.js +51 -11
- package/dist/modules/agent-instructions/shared-file-rules.js.map +1 -1
- package/dist/modules/auth/account-erasure.service.d.ts +36 -7
- package/dist/modules/auth/account-erasure.service.d.ts.map +1 -1
- package/dist/modules/auth/account-erasure.service.js +30 -10
- package/dist/modules/auth/account-erasure.service.js.map +1 -1
- package/dist/modules/auth/account-hooks.d.ts +20 -0
- package/dist/modules/auth/account-hooks.d.ts.map +1 -1
- package/dist/modules/auth/account-hooks.js +10 -0
- package/dist/modules/auth/account-hooks.js.map +1 -1
- package/dist/modules/auth/account-switch-lock.js +2 -2
- package/dist/modules/auth/account-switch-lock.js.map +1 -1
- package/dist/modules/auth/account.routes.d.ts +7 -2
- package/dist/modules/auth/account.routes.d.ts.map +1 -1
- package/dist/modules/auth/account.routes.js +23 -4
- package/dist/modules/auth/account.routes.js.map +1 -1
- package/dist/modules/auth/auth.service.d.ts +1 -1
- package/dist/modules/auth/auth.service.js +4 -4
- package/dist/modules/auth/auth.service.js.map +1 -1
- package/dist/modules/auth/index.d.ts +1 -0
- package/dist/modules/auth/index.d.ts.map +1 -1
- package/dist/modules/database/connection.d.ts.map +1 -1
- package/dist/modules/database/connection.js +11 -0
- package/dist/modules/database/connection.js.map +1 -1
- package/dist/modules/database/core-schema.js +2 -2
- package/dist/modules/database/core-schema.js.map +1 -1
- package/dist/modules/diff/diff.routes.d.ts +3 -3
- package/dist/modules/diff/diff.routes.d.ts.map +1 -1
- package/dist/modules/diff/diff.routes.js +7 -7
- package/dist/modules/diff/diff.routes.js.map +1 -1
- package/dist/modules/embed/embed.routes.js +1 -1
- package/dist/modules/embed/embed.routes.js.map +1 -1
- package/dist/modules/embed/embed.service.d.ts +4 -4
- package/dist/modules/embed/embed.service.d.ts.map +1 -1
- package/dist/modules/embed/embed.service.js +18 -18
- package/dist/modules/embed/embed.service.js.map +1 -1
- package/dist/modules/kb-fs/branch-name.js +8 -8
- package/dist/modules/kb-fs/branch-name.js.map +1 -1
- package/dist/modules/kb-fs/file-change-notifier.d.ts +51 -26
- package/dist/modules/kb-fs/file-change-notifier.d.ts.map +1 -1
- package/dist/modules/kb-fs/file-change-notifier.js +6 -8
- package/dist/modules/kb-fs/file-change-notifier.js.map +1 -1
- package/dist/modules/kb-fs/locking-filesystem.d.ts +9 -16
- package/dist/modules/kb-fs/locking-filesystem.d.ts.map +1 -1
- package/dist/modules/kb-fs/locking-filesystem.js +31 -39
- package/dist/modules/kb-fs/locking-filesystem.js.map +1 -1
- package/dist/modules/kb-fs/repo-path.js +5 -5
- package/dist/modules/kb-fs/repo-path.js.map +1 -1
- package/dist/modules/kb-fs/tmp-ref.d.ts +2 -2
- package/dist/modules/kb-fs/tmp-ref.d.ts.map +1 -1
- package/dist/modules/kb-fs/tmp-ref.js +2 -2
- package/dist/modules/kb-fs/tmp-ref.js.map +1 -1
- package/dist/modules/kb-sync/index.d.ts +1 -1
- package/dist/modules/kb-sync/index.d.ts.map +1 -1
- package/dist/modules/kb-sync/kb-sync.interface.d.ts +3 -3
- package/dist/modules/kb-sync/kb-sync.interface.d.ts.map +1 -1
- package/dist/modules/kb-sync/kb-sync.service.d.ts +3 -3
- package/dist/modules/kb-sync/kb-sync.service.d.ts.map +1 -1
- package/dist/modules/kb-sync/kb-sync.service.js +10 -10
- package/dist/modules/kb-sync/kb-sync.service.js.map +1 -1
- package/dist/modules/mcp/mcp.service.d.ts +11 -4
- package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
- package/dist/modules/mcp/mcp.service.js +75 -19
- package/dist/modules/mcp/mcp.service.js.map +1 -1
- package/dist/modules/onboarding/onboarding.contract.d.ts +1 -1
- package/dist/modules/onboarding/onboarding.routes.js +2 -2
- package/dist/modules/onboarding/onboarding.routes.js.map +1 -1
- package/dist/modules/onboarding/starter-pack.service.d.ts +5 -7
- package/dist/modules/onboarding/starter-pack.service.d.ts.map +1 -1
- package/dist/modules/onboarding/starter-pack.service.js +4 -4
- package/dist/modules/onboarding/starter-pack.service.js.map +1 -1
- package/dist/modules/plugins/join-request-jobs.service.d.ts +2 -2
- package/dist/modules/plugins/join-request-jobs.service.d.ts.map +1 -1
- package/dist/modules/plugins/join-request-jobs.service.js +7 -7
- package/dist/modules/plugins/join-request-jobs.service.js.map +1 -1
- package/dist/modules/plugins/join-requests.service.d.ts +3 -3
- package/dist/modules/plugins/join-requests.service.d.ts.map +1 -1
- package/dist/modules/plugins/join-requests.service.js +5 -5
- package/dist/modules/plugins/join-requests.service.js.map +1 -1
- package/dist/modules/plugins/plugin-rename.service.js +1 -1
- package/dist/modules/plugins/plugin-rename.service.js.map +1 -1
- package/dist/modules/plugins/plugins.routes.d.ts +2 -2
- package/dist/modules/plugins/plugins.routes.d.ts.map +1 -1
- package/dist/modules/plugins/plugins.routes.js +8 -8
- package/dist/modules/plugins/plugins.routes.js.map +1 -1
- package/dist/modules/secrets-vault/db-secrets-vault.service.d.ts +77 -2
- package/dist/modules/secrets-vault/db-secrets-vault.service.d.ts.map +1 -1
- package/dist/modules/secrets-vault/db-secrets-vault.service.js +479 -53
- package/dist/modules/secrets-vault/db-secrets-vault.service.js.map +1 -1
- package/dist/modules/secrets-vault/index.d.ts +2 -1
- package/dist/modules/secrets-vault/index.d.ts.map +1 -1
- package/dist/modules/secrets-vault/index.js +1 -0
- package/dist/modules/secrets-vault/index.js.map +1 -1
- package/dist/modules/secrets-vault/managed-sign-in-erasure.d.ts +36 -0
- package/dist/modules/secrets-vault/managed-sign-in-erasure.d.ts.map +1 -0
- package/dist/modules/secrets-vault/managed-sign-in-erasure.js +37 -0
- package/dist/modules/secrets-vault/managed-sign-in-erasure.js.map +1 -0
- package/dist/modules/secrets-vault/secrets-vault.contract.d.ts +152 -0
- package/dist/modules/secrets-vault/secrets-vault.contract.d.ts.map +1 -1
- package/dist/modules/secrets-vault/secrets-vault.contract.js.map +1 -1
- package/dist/modules/secrets-vault/secrets-vault.routes.d.ts +7 -1
- package/dist/modules/secrets-vault/secrets-vault.routes.d.ts.map +1 -1
- package/dist/modules/secrets-vault/secrets-vault.routes.js +125 -10
- package/dist/modules/secrets-vault/secrets-vault.routes.js.map +1 -1
- package/dist/modules/settings/connection-check.js +1 -1
- package/dist/modules/settings/connection-check.js.map +1 -1
- package/dist/modules/settings/deployment-settings.service.js +1 -1
- package/dist/modules/settings/git-root-folders.js +1 -1
- package/dist/modules/settings/git-root-folders.js.map +1 -1
- package/dist/modules/settings/setup.routes.d.ts +1 -1
- package/dist/modules/settings/setup.routes.js +1 -1
- package/dist/modules/skills/pending-skills.service.d.ts +3 -3
- package/dist/modules/skills/pending-skills.service.d.ts.map +1 -1
- package/dist/modules/skills/pending-skills.service.js +4 -4
- package/dist/modules/skills/pending-skills.service.js.map +1 -1
- package/dist/modules/skills/skill-access-requests.routes.d.ts +2 -2
- package/dist/modules/skills/skill-access-requests.routes.d.ts.map +1 -1
- package/dist/modules/skills/skill-access-requests.routes.js +5 -5
- package/dist/modules/skills/skill-access-requests.routes.js.map +1 -1
- package/dist/modules/tool-helpers/tool-context.d.ts +4 -4
- package/dist/modules/tool-helpers/tool-context.d.ts.map +1 -1
- package/dist/modules/tool-helpers/tool-context.js +2 -2
- package/dist/modules/tool-helpers/tool-context.js.map +1 -1
- package/dist/modules/tool-helpers/tool-handler.js +2 -2
- package/dist/modules/tool-helpers/tool-handler.js.map +1 -1
- package/dist/modules/tool-helpers/tool.contract.d.ts +4 -4
- package/dist/modules/tool-helpers/tool.contract.d.ts.map +1 -1
- package/dist/modules/tool-manuals/mcp-server-edit.service.js +1 -1
- package/dist/modules/tool-manuals/mcp-server-edit.service.js.map +1 -1
- package/dist/modules/tool-manuals/pending-tools.service.d.ts +3 -3
- package/dist/modules/tool-manuals/pending-tools.service.d.ts.map +1 -1
- package/dist/modules/tool-manuals/pending-tools.service.js +4 -4
- package/dist/modules/tool-manuals/pending-tools.service.js.map +1 -1
- package/dist/modules/tool-manuals/tool-delete.service.js +1 -1
- package/dist/modules/tool-manuals/tool-delete.service.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.contract.d.ts +43 -3
- package/dist/modules/tool-manuals/tool-manuals.contract.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.service.d.ts +6 -0
- package/dist/modules/tool-manuals/tool-manuals.service.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.service.js +51 -5
- package/dist/modules/tool-manuals/tool-manuals.service.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.tools.d.ts +3 -0
- package/dist/modules/tool-manuals/tool-manuals.tools.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.tools.js +36 -15
- package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
- package/dist/modules/version-control/agent-tools/change-request-read-shape.d.ts.map +1 -0
- package/dist/modules/version-control/agent-tools/change-request-read-shape.js.map +1 -0
- package/dist/modules/version-control/agent-tools/change-request-read.tools.d.ts.map +1 -0
- package/dist/modules/{workflow → version-control}/agent-tools/change-request-read.tools.js +2 -2
- package/dist/modules/version-control/agent-tools/change-request-read.tools.js.map +1 -0
- package/dist/modules/version-control/agent-tools/change-request-summary.d.ts.map +1 -0
- package/dist/modules/version-control/agent-tools/change-request-summary.js.map +1 -0
- package/dist/modules/{workflow/agent-tools/workflow.tools.d.ts → version-control/agent-tools/version-control.tools.d.ts} +4 -4
- package/dist/modules/version-control/agent-tools/version-control.tools.d.ts.map +1 -0
- package/dist/modules/{workflow/agent-tools/workflow.tools.js → version-control/agent-tools/version-control.tools.js} +31 -31
- package/dist/modules/version-control/agent-tools/version-control.tools.js.map +1 -0
- package/dist/modules/{workflow → version-control}/change-request-hooks.d.ts +1 -1
- package/dist/modules/version-control/change-request-hooks.d.ts.map +1 -0
- package/dist/modules/version-control/change-request-hooks.js.map +1 -0
- package/dist/modules/version-control/change-request-review/approval-lock.d.ts.map +1 -0
- package/dist/modules/version-control/change-request-review/approval-lock.js.map +1 -0
- package/dist/modules/{workflow/review-workflow/review-workflow.interface.d.ts → version-control/change-request-review/change-request-review.interface.d.ts} +2 -2
- package/dist/modules/version-control/change-request-review/change-request-review.interface.d.ts.map +1 -0
- package/dist/modules/version-control/change-request-review/change-request-review.interface.js +2 -0
- package/dist/modules/version-control/change-request-review/change-request-review.interface.js.map +1 -0
- package/dist/modules/{workflow/review-workflow/review-workflow.service.d.ts → version-control/change-request-review/change-request-review.service.d.ts} +3 -3
- package/dist/modules/version-control/change-request-review/change-request-review.service.d.ts.map +1 -0
- package/dist/modules/{workflow/review-workflow/review-workflow.service.js → version-control/change-request-review/change-request-review.service.js} +26 -26
- package/dist/modules/version-control/change-request-review/change-request-review.service.js.map +1 -0
- package/dist/modules/{workflow → version-control}/event-bus.d.ts +8 -8
- package/dist/modules/version-control/event-bus.d.ts.map +1 -0
- package/dist/modules/{workflow → version-control}/event-bus.js +4 -4
- package/dist/modules/version-control/event-bus.js.map +1 -0
- package/dist/modules/{workflow → version-control}/events-delivery.d.ts +4 -4
- package/dist/modules/version-control/events-delivery.d.ts.map +1 -0
- package/dist/modules/version-control/events-delivery.js.map +1 -0
- package/dist/modules/{workflow → version-control}/events.routes.d.ts +4 -4
- package/dist/modules/version-control/events.routes.d.ts.map +1 -0
- package/dist/modules/version-control/events.routes.js.map +1 -0
- package/dist/modules/{workflow → version-control}/file-lock.service.d.ts +2 -2
- package/dist/modules/version-control/file-lock.service.d.ts.map +1 -0
- package/dist/modules/{workflow → version-control}/file-lock.service.js +6 -6
- package/dist/modules/version-control/file-lock.service.js.map +1 -0
- package/dist/modules/version-control/git/change-request-link.d.ts.map +1 -0
- package/dist/modules/version-control/git/change-request-link.js.map +1 -0
- package/dist/modules/version-control/git/git-version.d.ts.map +1 -0
- package/dist/modules/version-control/git/git-version.js.map +1 -0
- package/dist/modules/{workflow → version-control}/git/git.service.d.ts +26 -8
- package/dist/modules/version-control/git/git.service.d.ts.map +1 -0
- package/dist/modules/{workflow → version-control}/git/git.service.js +106 -55
- package/dist/modules/version-control/git/git.service.js.map +1 -0
- package/dist/modules/version-control/git/merge-commit.d.ts.map +1 -0
- package/dist/modules/version-control/git/merge-commit.js.map +1 -0
- package/dist/modules/version-control/git/node-git-runner.d.ts.map +1 -0
- package/dist/modules/{workflow → version-control}/git/node-git-runner.js +1 -1
- package/dist/modules/version-control/git/node-git-runner.js.map +1 -0
- package/dist/modules/{workflow → version-control}/git/pull-request.service.d.ts +5 -5
- package/dist/modules/version-control/git/pull-request.service.d.ts.map +1 -0
- package/dist/modules/{workflow → version-control}/git/pull-request.service.js +10 -10
- package/dist/modules/version-control/git/pull-request.service.js.map +1 -0
- package/dist/modules/{workflow → version-control}/index.d.ts +6 -5
- package/dist/modules/version-control/index.d.ts.map +1 -0
- package/dist/modules/{workflow → version-control}/index.js +3 -2
- package/dist/modules/version-control/index.js.map +1 -0
- package/dist/modules/{workflow → version-control}/pending-commits.service.d.ts +2 -2
- package/dist/modules/version-control/pending-commits.service.d.ts.map +1 -0
- package/dist/modules/{workflow → version-control}/pending-commits.service.js +2 -2
- package/dist/modules/version-control/pending-commits.service.js.map +1 -0
- package/dist/modules/{workflow → version-control}/pending-commits.worker.d.ts +5 -5
- package/dist/modules/version-control/pending-commits.worker.d.ts.map +1 -0
- package/dist/modules/{workflow → version-control}/pending-commits.worker.js +3 -3
- package/dist/modules/version-control/pending-commits.worker.js.map +1 -0
- package/dist/modules/{workflow → version-control}/read-on-branch.d.ts +2 -2
- package/dist/modules/version-control/read-on-branch.d.ts.map +1 -0
- package/dist/modules/{workflow → version-control}/read-on-branch.js +2 -2
- package/dist/modules/version-control/read-on-branch.js.map +1 -0
- package/dist/modules/version-control/recovery-bot.d.ts.map +1 -0
- package/dist/modules/version-control/recovery-bot.js.map +1 -0
- package/dist/modules/version-control/sanitize-error.d.ts.map +1 -0
- package/dist/modules/version-control/sanitize-error.js.map +1 -0
- package/dist/modules/{workflow/workflow-hooks.d.ts → version-control/version-control-hooks.d.ts} +33 -6
- package/dist/modules/version-control/version-control-hooks.d.ts.map +1 -0
- package/dist/modules/{workflow/workflow-hooks.js → version-control/version-control-hooks.js} +20 -3
- package/dist/modules/version-control/version-control-hooks.js.map +1 -0
- package/dist/modules/{workflow/workflow.routes.d.ts → version-control/version-control.routes.d.ts} +6 -5
- package/dist/modules/version-control/version-control.routes.d.ts.map +1 -0
- package/dist/modules/{workflow/workflow.routes.js → version-control/version-control.routes.js} +61 -60
- package/dist/modules/version-control/version-control.routes.js.map +1 -0
- package/dist/modules/{workflow/workflow.service.d.ts → version-control/version-control.service.d.ts} +26 -46
- package/dist/modules/version-control/version-control.service.d.ts.map +1 -0
- package/dist/modules/{workflow/workflow.service.js → version-control/version-control.service.js} +93 -107
- package/dist/modules/version-control/version-control.service.js.map +1 -0
- package/dist/modules/workspace/agent-access.gate.d.ts +5 -5
- package/dist/modules/workspace/agent-access.gate.d.ts.map +1 -1
- package/dist/modules/workspace/agent-access.gate.js +1 -1
- package/dist/modules/workspace/git-internals.middleware.d.ts +1 -1
- package/dist/modules/workspace/git-internals.middleware.js +2 -2
- package/dist/modules/workspace/kb-repository.js +1 -1
- package/dist/modules/workspace/kb-repository.js.map +1 -1
- package/dist/modules/workspace/startup/kb-git.d.ts +1 -1
- package/dist/modules/workspace/startup/kb-git.d.ts.map +1 -1
- package/dist/modules/workspace/startup/kb-git.js +1 -1
- package/dist/modules/workspace/startup/kb-git.js.map +1 -1
- package/dist/modules/workspace/startup/steps/seed-tree.d.ts +2 -2
- package/dist/modules/workspace/startup/steps/seed-tree.d.ts.map +1 -1
- package/dist/modules/workspace/startup/steps/seed-tree.js +1 -1
- package/dist/modules/workspace/startup/steps/seed-tree.js.map +1 -1
- package/dist/modules/workspace/startup/steps/template-files.step.d.ts +2 -2
- package/dist/modules/workspace/startup/steps/template-files.step.d.ts.map +1 -1
- package/dist/modules/workspace/startup/steps/template-files.step.js +2 -2
- package/dist/modules/workspace/startup/steps/template-files.step.js.map +1 -1
- package/dist/modules/workspace/startup/steps/template-source.d.ts +17 -4
- package/dist/modules/workspace/startup/steps/template-source.d.ts.map +1 -1
- package/dist/modules/workspace/startup/steps/template-source.js +8 -5
- package/dist/modules/workspace/startup/steps/template-source.js.map +1 -1
- package/dist/modules/workspace/workspace-tmp.js +3 -3
- package/dist/modules/workspace/workspace-tmp.js.map +1 -1
- package/dist/modules/workspace/workspace.routes.d.ts +4 -4
- package/dist/modules/workspace/workspace.routes.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.routes.js +22 -22
- package/dist/modules/workspace/workspace.routes.js.map +1 -1
- package/dist/modules/workspace/workspace.service.d.ts +6 -6
- package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.service.js +11 -11
- package/dist/modules/workspace/workspace.service.js.map +1 -1
- package/dist/modules/workspace/workspace.tools.d.ts +2 -1
- package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.tools.js +15 -10
- package/dist/modules/workspace/workspace.tools.js.map +1 -1
- package/dist/shared/canonical-file-identity.d.ts +2 -2
- package/dist/shared/canonical-file-identity.js +5 -5
- package/dist/shared/canonical-file-identity.js.map +1 -1
- package/dist/shared/domain-errors.d.ts +39 -39
- package/dist/shared/domain-errors.d.ts.map +1 -1
- package/dist/shared/domain-errors.js +44 -44
- package/dist/shared/domain-errors.js.map +1 -1
- package/dist/shared/fire-and-forget.d.ts +14 -0
- package/dist/shared/fire-and-forget.d.ts.map +1 -0
- package/dist/shared/fire-and-forget.js +23 -0
- package/dist/shared/fire-and-forget.js.map +1 -0
- package/dist/shared/git.contract.d.ts +1 -1
- package/dist/shared/git.contract.js +1 -1
- package/dist/shared/http-errors.d.ts +3 -3
- package/dist/shared/http-errors.d.ts.map +1 -1
- package/dist/shared/http-errors.js +1 -1
- package/dist/shared/http-errors.js.map +1 -1
- package/dist/shared/pending-proposals.d.ts +3 -3
- package/dist/shared/pending-proposals.d.ts.map +1 -1
- package/dist/shared/pending-proposals.js +2 -2
- package/dist/shared/pending-proposals.js.map +1 -1
- package/dist/tenancy/static-tenant-source.js +1 -1
- package/dist/tenancy/static-tenant-source.js.map +1 -1
- package/migrations/0012_agent_audit.sql +40 -40
- package/migrations/0013_agent_identity_and_key_soft_delete.sql +29 -29
- package/migrations/meta/0012_snapshot.json +2246 -2246
- package/migrations/meta/0013_snapshot.json +2258 -2258
- package/package.json +4 -4
- package/src/__tests__/account-admin.ts +27 -0
- package/src/__tests__/capturing-logger.ts +36 -0
- package/src/__tests__/hexis-catalog.ts +4 -4
- package/src/__tests__/kb-context.ts +30 -30
- package/src/__tests__/kb-layout-config.test.ts +21 -0
- package/src/__tests__/open-change-gate.ts +1 -1
- package/src/__tests__/real-access.ts +16 -0
- package/src/__tests__/real-auth.ts +13 -0
- package/src/__tests__/real-readers.ts +27 -0
- package/src/__tests__/real-version-control.ts +22 -0
- package/src/__tests__/tool-handler.ts +15 -0
- package/src/__tests__/tool-wiring.ts +10 -4
- package/src/__tests__/workspace-service.ts +1 -1
- package/src/core/__tests__/catalog-cache-invalidation.test.ts +3 -3
- package/src/core/__tests__/commit-announcements-wired-once.test.ts +53 -0
- package/src/core/__tests__/compose-embed.test.ts +1 -1
- package/src/core/__tests__/damaged-document.tools.test.ts +3 -3
- package/src/core/__tests__/erasure-slices-wired-once.test.ts +46 -0
- package/src/core/__tests__/gated-boot.test.ts +2 -2
- package/src/core/__tests__/lifecycle.test.ts +9 -9
- package/src/core/catalog-cache-invalidation.ts +2 -2
- package/src/core/compose-embed.ts +3 -3
- package/src/core/core-ports.ts +19 -4
- package/src/core/create-core-server.ts +39 -25
- package/src/core/create-core-services.ts +83 -65
- package/src/core/lifecycle.ts +9 -9
- package/src/core-config.ts +3 -3
- package/src/index.ts +5 -5
- package/src/modules/access/__tests__/access-control.admin-root-floor.test.ts +4 -4
- package/src/modules/access/__tests__/access-own-read-grant.test.ts +14 -14
- package/src/modules/access/__tests__/access-requests.routes.test.ts +23 -23
- package/src/modules/access/__tests__/access.routes.admins.test.ts +4 -4
- package/src/modules/access/__tests__/access.routes.batch.test.ts +4 -4
- package/src/modules/access/__tests__/access.routes.folder-governs.test.ts +6 -6
- package/src/modules/access/__tests__/access.routes.group-grant.test.ts +6 -6
- package/src/modules/access/__tests__/access.routes.overrides.test.ts +5 -5
- package/src/modules/access/__tests__/access.routes.proposal-branch.test.ts +6 -6
- package/src/modules/access/__tests__/access.routes.prospective.test.ts +3 -3
- package/src/modules/access/__tests__/access.routes.revoke.test.ts +6 -6
- package/src/modules/access/__tests__/access.routes.unknown-email.test.ts +6 -6
- package/src/modules/access/__tests__/admin-locked-commit.test.ts +6 -6
- package/src/modules/access/__tests__/admin-route-helpers.test.ts +5 -5
- package/src/modules/access/__tests__/groups-admin.service.test.ts +44 -44
- package/src/modules/access/__tests__/roles-admin.security.test.ts +14 -13
- package/src/modules/access/__tests__/roles-admin.service.test.ts +34 -34
- package/src/modules/access/__tests__/roles-capabilities.test.ts +29 -29
- package/src/modules/access/__tests__/roles.routes.test.ts +11 -11
- package/src/modules/access/__tests__/synced-groups-committer.test.ts +14 -14
- package/src/modules/access/__tests__/user-access-removal.service.test.ts +33 -33
- package/src/modules/access/access-control.service.ts +1 -1
- package/src/modules/access/access-declarations.ts +2 -2
- package/src/modules/access/access-mutation.service.ts +2 -2
- package/src/modules/access/access-requests.routes.ts +7 -7
- package/src/modules/access/access-requests.service.ts +13 -13
- package/src/modules/access/access.routes.ts +11 -11
- package/src/modules/access/admin-locked-commit.ts +11 -11
- package/src/modules/access/admin-route-helpers.ts +5 -5
- package/src/modules/access/change-read-gate.ts +1 -1
- package/src/modules/access/groups-admin.service.ts +7 -7
- package/src/modules/access/reference-scan.ts +1 -1
- package/src/modules/access/roles-admin.service.ts +9 -9
- package/src/modules/access/synced-groups-committer.ts +11 -11
- package/src/modules/access/user-access-removal.service.ts +7 -7
- package/src/modules/access-model/__tests__/access-grammar.test.ts +2 -2
- package/src/modules/access-model/access-errors.ts +4 -4
- package/src/modules/access-model/access-grammar.ts +4 -4
- package/src/modules/access-model/change-gate.ts +1 -1
- package/src/modules/access-model/roles-yaml-guard.ts +4 -4
- package/src/modules/agent-guide/__tests__/agent-guide.test.ts +19 -4
- package/src/modules/agent-guide/__tests__/agent-guide.tools.test.ts +1 -1
- package/src/modules/agent-guide/__tests__/guide-matches-code.test.ts +2 -2
- package/src/modules/agent-guide/agent-guide.ts +48 -22
- package/src/modules/agent-guide/guide-budget.ts +6 -2
- package/src/modules/agent-instructions/__tests__/agent-instructions.route.test.ts +4 -5
- package/src/modules/agent-instructions/__tests__/compose.test.ts +1 -1
- package/src/modules/agent-instructions/__tests__/shared-file-rules.test.ts +113 -7
- package/src/modules/agent-instructions/agent-instructions.routes.ts +12 -5
- package/src/modules/agent-instructions/compose.ts +24 -9
- package/src/modules/agent-instructions/shared-file-rules.ts +53 -11
- package/src/modules/agent-scope/__tests__/agent-surfaces.connect-as.test.ts +1 -1
- package/src/modules/audit/__tests__/agent-rest-audit.middleware.test.ts +113 -113
- package/src/modules/audit/__tests__/event-classifier.test.ts +121 -121
- package/src/modules/audit/event-classifier.ts +155 -155
- package/src/modules/auth/__tests__/account-deactivation.test.ts +1 -1
- package/src/modules/auth/__tests__/account-erasure.approval-gate.test.ts +4 -4
- package/src/modules/auth/__tests__/account-erasure.participants.test.ts +56 -0
- package/src/modules/auth/__tests__/account-switch-lock.pg.test.ts +2 -2
- package/src/modules/auth/__tests__/account-switch-lock.test.ts +2 -2
- package/src/modules/auth/__tests__/account.routes.test.ts +73 -11
- package/src/modules/auth/__tests__/auth.service.test.ts +1 -1
- package/src/modules/auth/account-erasure.service.ts +27 -9
- package/src/modules/auth/account-hooks.ts +34 -0
- package/src/modules/auth/account-switch-lock.ts +2 -2
- package/src/modules/auth/account.routes.ts +23 -5
- package/src/modules/auth/auth.service.ts +4 -4
- package/src/modules/auth/index.ts +1 -0
- package/src/modules/database/__tests__/advisory-lock.test.ts +169 -169
- package/src/modules/database/__tests__/connection.pg.test.ts +55 -0
- package/src/modules/database/__tests__/connection.test.ts +30 -0
- package/src/modules/database/__tests__/migrations-unqualified.test.ts +32 -32
- package/src/modules/database/connection.ts +12 -0
- package/src/modules/database/core-schema.ts +2 -2
- package/src/modules/diff/__tests__/diff.routes.read-gate.test.ts +2 -2
- package/src/modules/diff/__tests__/diff.routes.rejectPathsLocked.test.ts +12 -12
- package/src/modules/diff/diff.routes.ts +8 -8
- package/src/modules/embed/__tests__/embed.service.lock.test.ts +7 -7
- package/src/modules/embed/__tests__/embed.service.test.ts +48 -62
- package/src/modules/embed/__tests__/embed.tools.test.ts +1 -1
- package/src/modules/embed/embed.routes.ts +1 -1
- package/src/modules/embed/embed.service.ts +17 -17
- package/src/modules/kb-fs/__tests__/branch-name.test.ts +2 -2
- package/src/modules/kb-fs/__tests__/file-change-notifier.test.ts +23 -4
- package/src/modules/kb-fs/__tests__/locking-filesystem.test.ts +264 -305
- package/src/modules/kb-fs/__tests__/move-with-edits.test.ts +103 -40
- package/src/modules/kb-fs/__tests__/repo-path.test.ts +8 -8
- package/src/modules/kb-fs/branch-name.ts +8 -8
- package/src/modules/kb-fs/file-change-notifier.ts +55 -32
- package/src/modules/kb-fs/locking-filesystem.ts +34 -48
- package/src/modules/kb-fs/repo-path.ts +5 -5
- package/src/modules/kb-fs/tmp-ref.ts +3 -3
- package/src/modules/kb-sync/__tests__/kb-sync.service.test.ts +36 -36
- package/src/modules/kb-sync/index.ts +1 -1
- package/src/modules/kb-sync/kb-sync.interface.ts +3 -3
- package/src/modules/kb-sync/kb-sync.service.ts +9 -9
- package/src/modules/marketplace/__tests__/github-facade.test.ts +1 -1
- package/src/modules/marketplace/__tests__/marketplace-git.test.ts +1 -1
- package/src/modules/mcp/__tests__/mcp-proxy-helpers.test.ts +4 -2
- package/src/modules/mcp/__tests__/mcp.e2e.test.ts +69 -8
- package/src/modules/mcp/__tests__/mcp.service.test.ts +91 -13
- package/src/modules/mcp/__tests__/read-only-hint.pg.test.ts +194 -0
- package/src/modules/mcp/mcp.service.ts +89 -28
- package/src/modules/onboarding/__tests__/starter-pack.service.test.ts +59 -59
- package/src/modules/onboarding/onboarding.contract.ts +1 -1
- package/src/modules/onboarding/onboarding.routes.ts +2 -2
- package/src/modules/onboarding/starter-pack.service.ts +7 -9
- package/src/modules/plugins/__tests__/bundle-dialect.e2e.test.ts +1 -1
- package/src/modules/plugins/__tests__/compile-marketplace.test.ts +1 -1
- package/src/modules/plugins/__tests__/join-request-jobs.service.test.ts +43 -43
- package/src/modules/plugins/__tests__/join-requests.service.test.ts +19 -19
- package/src/modules/plugins/__tests__/join-requests.settlement.test.ts +26 -26
- package/src/modules/plugins/__tests__/plugin-rename.service.test.ts +46 -21
- package/src/modules/plugins/__tests__/plugins.routes.test.ts +31 -31
- package/src/modules/plugins/join-request-jobs.service.ts +10 -10
- package/src/modules/plugins/join-requests.service.ts +4 -4
- package/src/modules/plugins/plugin-rename.service.ts +1 -1
- package/src/modules/plugins/plugins.routes.ts +9 -9
- package/src/modules/secrets-vault/__tests__/db-secrets-vault.managed.test.ts +773 -0
- package/src/modules/secrets-vault/__tests__/db-secrets-vault.oauth.test.ts +28 -1
- package/src/modules/secrets-vault/__tests__/managed-sign-in.route.test.ts +351 -0
- package/src/modules/secrets-vault/db-secrets-vault.service.ts +562 -95
- package/src/modules/secrets-vault/index.ts +7 -0
- package/src/modules/secrets-vault/managed-sign-in-erasure.ts +41 -0
- package/src/modules/secrets-vault/secrets-variable-loader.ts +134 -134
- package/src/modules/secrets-vault/secrets-vault.contract.ts +152 -0
- package/src/modules/secrets-vault/secrets-vault.routes.ts +157 -10
- package/src/modules/settings/__tests__/repository-source.test.ts +1 -1
- package/src/modules/settings/__tests__/setup.routes.layout-phase.test.ts +1 -1
- package/src/modules/settings/__tests__/setup.routes.managed-phase.test.ts +1 -1
- package/src/modules/settings/connection-check.ts +1 -1
- package/src/modules/settings/deployment-settings.service.ts +1 -1
- package/src/modules/settings/git-root-folders.ts +1 -1
- package/src/modules/settings/setup.routes.ts +1 -1
- package/src/modules/skills/__tests__/allowed-tools-warn.tools.test.ts +3 -3
- package/src/modules/skills/__tests__/branch-skills.tools.test.ts +1 -1
- package/src/modules/skills/__tests__/pending-skills.service.test.ts +8 -8
- package/src/modules/skills/__tests__/skill-access-requests.routes.test.ts +18 -18
- package/src/modules/skills/__tests__/skills.service.test.ts +3 -3
- package/src/modules/skills/pending-skills.service.ts +3 -3
- package/src/modules/skills/skill-access-requests.routes.ts +7 -7
- package/src/modules/tool-helpers/__tests__/agent-roles-write.test.ts +4 -4
- package/src/modules/tool-helpers/__tests__/branch-declaration.test.ts +3 -3
- package/src/modules/tool-helpers/__tests__/can-write.tools.test.ts +3 -3
- package/src/modules/tool-helpers/__tests__/phase4-tools.test.ts +4 -4
- package/src/modules/tool-helpers/__tests__/tools-surface.test.ts +3 -3
- package/src/modules/tool-helpers/__tests__/validate-token.test.ts +1 -1
- package/src/modules/tool-helpers/tool-context.ts +6 -6
- package/src/modules/tool-helpers/tool-handler.ts +2 -2
- package/src/modules/tool-helpers/tool.contract.ts +4 -4
- package/src/modules/tool-manuals/__tests__/pending-tools.service.test.ts +9 -9
- package/src/modules/tool-manuals/__tests__/tool-manuals.managed-sign-in.test.ts +89 -0
- package/src/modules/tool-manuals/__tests__/tool-manuals.restart.test.ts +1 -1
- package/src/modules/tool-manuals/__tests__/tool-manuals.tools.test.ts +36 -1
- package/src/modules/tool-manuals/mcp-server-edit.service.ts +1 -1
- package/src/modules/tool-manuals/pending-tools.service.ts +3 -3
- package/src/modules/tool-manuals/tool-delete.service.ts +1 -1
- package/src/modules/tool-manuals/tool-manuals.contract.ts +41 -3
- package/src/modules/tool-manuals/tool-manuals.service.ts +53 -5
- package/src/modules/tool-manuals/tool-manuals.tools.ts +41 -15
- package/src/modules/tool-registry/__tests__/own-tool-schemas.test.ts +4 -4
- package/src/modules/tool-registry/__tests__/read-only-hints.test.ts +3 -3
- package/src/modules/tool-registry/__tests__/tool-description-length.test.ts +6 -6
- package/src/modules/{workflow → version-control}/__tests__/apply-failure.test.ts +54 -54
- package/src/modules/{workflow → version-control}/__tests__/event-bus.test.ts +27 -27
- package/src/modules/{workflow → version-control}/__tests__/events-delivery.test.ts +37 -37
- package/src/modules/{workflow → version-control}/__tests__/file-lock.service.deletion-gate.test.ts +2 -2
- package/src/modules/{workflow → version-control}/__tests__/file-lock.service.path-identity.test.ts +8 -8
- package/src/modules/{workflow → version-control}/__tests__/merge-announces-tree.test.ts +11 -11
- package/src/modules/{workflow → version-control}/__tests__/pending-commits.worker.concurrency.test.ts +9 -9
- package/src/modules/{workflow → version-control}/__tests__/pending-commits.worker.test.ts +25 -25
- package/src/modules/{workflow → version-control}/__tests__/preserve-roles-yaml.test.ts +42 -42
- package/src/modules/version-control/__tests__/public-surface.test.ts +114 -0
- package/src/modules/{workflow/__tests__/workflow.routes.folder-proposals.test.ts → version-control/__tests__/version-control.routes.folder-proposals.test.ts} +18 -18
- package/src/modules/{workflow/__tests__/workflow.routes.fork-point-file.test.ts → version-control/__tests__/version-control.routes.fork-point-file.test.ts} +19 -19
- package/src/modules/{workflow/__tests__/workflow.routes.history-read-gate.test.ts → version-control/__tests__/version-control.routes.history-read-gate.test.ts} +13 -13
- package/src/modules/{workflow/__tests__/workflow.routes.lock-path.test.ts → version-control/__tests__/version-control.routes.lock-path.test.ts} +18 -18
- package/src/modules/{workflow/__tests__/workflow.routes.open-change-request.test.ts → version-control/__tests__/version-control.routes.open-change-request.test.ts} +7 -7
- package/src/modules/{workflow/__tests__/workflow.routes.push-refused.test.ts → version-control/__tests__/version-control.routes.push-refused.test.ts} +14 -14
- package/src/modules/{workflow/__tests__/workflow.service.branch-in-use.test.ts → version-control/__tests__/version-control.service.branch-in-use.test.ts} +6 -6
- package/src/modules/{workflow/__tests__/workflow.service.commitFileWhileLocked.test.ts → version-control/__tests__/version-control.service.commitFileWhileLocked.test.ts} +14 -14
- package/src/modules/{workflow/__tests__/workflow.service.delete-branch.test.ts → version-control/__tests__/version-control.service.delete-branch.test.ts} +14 -14
- package/src/modules/{workflow/__tests__/workflow.service.deleted-branch-sweep.test.ts → version-control/__tests__/version-control.service.deleted-branch-sweep.test.ts} +4 -4
- package/src/modules/{workflow/__tests__/workflow.service.facade.test.ts → version-control/__tests__/version-control.service.facade.test.ts} +100 -102
- package/src/modules/{workflow/__tests__/workflow.service.folder-proposals.test.ts → version-control/__tests__/version-control.service.folder-proposals.test.ts} +7 -7
- package/src/modules/{workflow/__tests__/workflow.service.platform-restore.test.ts → version-control/__tests__/version-control.service.platform-restore.test.ts} +6 -6
- package/src/modules/{workflow/__tests__/workflow.service.read-gate.test.ts → version-control/__tests__/version-control.service.read-gate.test.ts} +9 -9
- package/src/modules/{workflow/__tests__/workflow.service.releaseLock.test.ts → version-control/__tests__/version-control.service.releaseLock.test.ts} +85 -75
- package/src/modules/{workflow/__tests__/workflow.service.repository-replaced.test.ts → version-control/__tests__/version-control.service.repository-replaced.test.ts} +4 -4
- package/src/modules/{workflow/__tests__/workflow.service.sync.test.ts → version-control/__tests__/version-control.service.sync.test.ts} +17 -17
- package/src/modules/{workflow/__tests__/workflow.service.update-change-request.pg.test.ts → version-control/__tests__/version-control.service.update-change-request.pg.test.ts} +6 -6
- package/src/modules/{workflow/__tests__/workflow.service.update-change-request.test.ts → version-control/__tests__/version-control.service.update-change-request.test.ts} +11 -11
- package/src/modules/{workflow/__tests__/workflow.service.update-from-target.test.ts → version-control/__tests__/version-control.service.update-from-target.test.ts} +22 -22
- package/src/modules/{workflow → version-control}/agent-tools/__tests__/change-request-read.tools.test.ts +3 -3
- package/src/modules/{workflow → version-control}/agent-tools/__tests__/update-change-request.parity.test.ts +16 -16
- package/src/modules/{workflow/agent-tools/__tests__/workflow.tools.test.ts → version-control/agent-tools/__tests__/version-control.tools.test.ts} +9 -9
- package/src/modules/{workflow → version-control}/agent-tools/change-request-read.tools.ts +2 -2
- package/src/modules/{workflow/agent-tools/workflow.tools.ts → version-control/agent-tools/version-control.tools.ts} +30 -30
- package/src/modules/{workflow → version-control}/change-request-hooks.ts +1 -1
- package/src/modules/{workflow/review-workflow → version-control/change-request-review}/__tests__/approval-states.test.ts +5 -5
- package/src/modules/{workflow/review-workflow → version-control/change-request-review}/__tests__/cancel-pr.test.ts +8 -8
- package/src/modules/{workflow/review-workflow → version-control/change-request-review}/__tests__/carry-approvals-forward.test.ts +6 -6
- package/src/modules/{workflow/review-workflow → version-control/change-request-review}/__tests__/erase-approver.test.ts +5 -5
- package/src/modules/{workflow/review-workflow → version-control/change-request-review}/__tests__/merge-gate.test.ts +3 -3
- package/src/modules/{workflow/review-workflow → version-control/change-request-review}/__tests__/merge-records-own-commit.test.ts +3 -3
- package/src/modules/{workflow/review-workflow/review-workflow.interface.ts → version-control/change-request-review/change-request-review.interface.ts} +1 -1
- package/src/modules/{workflow/review-workflow/review-workflow.service.ts → version-control/change-request-review/change-request-review.service.ts} +28 -28
- package/src/modules/{workflow → version-control}/event-bus.ts +14 -14
- package/src/modules/{workflow → version-control}/events-delivery.ts +11 -11
- package/src/modules/{workflow → version-control}/events.routes.ts +5 -5
- package/src/modules/{workflow → version-control}/file-lock.service.ts +6 -6
- package/src/modules/{workflow → version-control}/git/__tests__/git-test-helpers.ts +3 -3
- package/src/modules/{workflow → version-control}/git/__tests__/git.service.accessGating.test.ts +5 -5
- package/src/modules/{workflow → version-control}/git/__tests__/git.service.appliedChange.test.ts +4 -4
- package/src/modules/{workflow → version-control}/git/__tests__/git.service.changedFilesForPr.test.ts +16 -16
- package/src/modules/{workflow → version-control}/git/__tests__/git.service.changedPathsMemo.test.ts +2 -2
- package/src/modules/{workflow → version-control}/git/__tests__/git.service.commitChanges.test.ts +3 -3
- package/src/modules/{workflow → version-control}/git/__tests__/git.service.commitFile.strayPath.test.ts +10 -10
- package/src/modules/{workflow → version-control}/git/__tests__/git.service.commitFile.test.ts +3 -3
- package/src/modules/version-control/git/__tests__/git.service.commitLanded.test.ts +199 -0
- package/src/modules/{workflow → version-control}/git/__tests__/git.service.createBranch.test.ts +3 -3
- package/src/modules/{workflow → version-control}/git/__tests__/git.service.deleteBranch.test.ts +7 -7
- package/src/modules/{workflow → version-control}/git/__tests__/git.service.diffFileAtCommit.test.ts +9 -9
- package/src/modules/{workflow → version-control}/git/__tests__/git.service.diffFileBetweenBranches.test.ts +23 -23
- package/src/modules/{workflow → version-control}/git/__tests__/git.service.fileBytesAtCommit.test.ts +7 -7
- package/src/modules/{workflow → version-control}/git/__tests__/git.service.fileContentsAtCommit.test.ts +3 -3
- package/src/modules/{workflow → version-control}/git/__tests__/git.service.forkPoint.test.ts +2 -2
- package/src/modules/{workflow → version-control}/git/__tests__/git.service.history-guards.test.ts +2 -2
- package/src/modules/{workflow → version-control}/git/__tests__/git.service.list-branches.test.ts +12 -12
- package/src/modules/{workflow → version-control}/git/__tests__/git.service.listTrackedFiles.test.ts +2 -2
- package/src/modules/{workflow → version-control}/git/__tests__/git.service.logForFile.test.ts +6 -6
- package/src/modules/{workflow → version-control}/git/__tests__/git.service.mergeChangeRequest.test.ts +99 -22
- package/src/modules/{workflow → version-control}/git/__tests__/git.service.mergeIntoBranch.test.ts +16 -16
- package/src/modules/{workflow → version-control}/git/__tests__/git.service.pendingChanges.test.ts +2 -2
- package/src/modules/{workflow → version-control}/git/__tests__/git.service.postMergePull.test.ts +5 -5
- package/src/modules/{workflow → version-control}/git/__tests__/git.service.prFetchFailure.test.ts +2 -2
- package/src/modules/{workflow → version-control}/git/__tests__/git.service.pull.test.ts +12 -12
- package/src/modules/{workflow → version-control}/git/__tests__/git.service.readFileAtRef.test.ts +3 -3
- package/src/modules/{workflow → version-control}/git/__tests__/git.service.revertPathsAndPush.test.ts +2 -2
- package/src/modules/{workflow → version-control}/git/__tests__/git.service.status.test.ts +4 -4
- package/src/modules/{workflow → version-control}/git/__tests__/git.service.syncFromRemote.test.ts +8 -8
- package/src/modules/{workflow → version-control}/git/__tests__/git.service.worktrees.test.ts +4 -4
- package/src/modules/{workflow → version-control}/git/__tests__/pull-request.service.list-fetch.test.ts +2 -2
- package/src/modules/{workflow → version-control}/git/__tests__/pull-request.service.test.ts +6 -6
- package/src/modules/{workflow → version-control}/git/git.service.ts +112 -59
- package/src/modules/{workflow → version-control}/git/node-git-runner.ts +1 -1
- package/src/modules/{workflow → version-control}/git/pull-request.service.ts +12 -12
- package/src/modules/{workflow → version-control}/index.ts +6 -5
- package/src/modules/{workflow → version-control}/pending-commits.service.ts +2 -2
- package/src/modules/{workflow → version-control}/pending-commits.worker.ts +8 -8
- package/src/modules/{workflow → version-control}/read-on-branch.ts +2 -2
- package/src/modules/{workflow/workflow-hooks.ts → version-control/version-control-hooks.ts} +44 -5
- package/src/modules/{workflow/workflow.routes.ts → version-control/version-control.routes.ts} +64 -63
- package/src/modules/{workflow/workflow.service.ts → version-control/version-control.service.ts} +96 -109
- package/src/modules/workspace/__tests__/agent-access.gate.test.ts +8 -8
- package/src/modules/workspace/__tests__/agent-downloads.test.ts +13 -16
- package/src/modules/workspace/__tests__/agent-uploads.test.ts +11 -12
- package/src/modules/workspace/__tests__/escape-sequences.routes.test.ts +11 -16
- package/src/modules/workspace/__tests__/file-stat-access.test.ts +3 -3
- package/src/modules/workspace/__tests__/git-internals.security.test.ts +27 -27
- package/src/modules/workspace/__tests__/kb-repository.test.ts +1 -1
- package/src/modules/workspace/__tests__/rest-tool-arguments.test.ts +3 -3
- package/src/modules/workspace/__tests__/start-session.default-branch.test.ts +152 -0
- package/src/modules/workspace/__tests__/workspace-paths-inside-the-repo.test.ts +6 -6
- package/src/modules/workspace/__tests__/workspace-tmp.tools.test.ts +13 -14
- package/src/modules/workspace/__tests__/workspace.routes.at-ref.test.ts +6 -6
- package/src/modules/workspace/__tests__/workspace.routes.branch-errors.test.ts +4 -4
- package/src/modules/workspace/__tests__/workspace.routes.create-grant.test.ts +6 -6
- package/src/modules/workspace/__tests__/workspace.routes.delete.test.ts +8 -8
- package/src/modules/workspace/__tests__/workspace.routes.download-implies-read.test.ts +4 -4
- package/src/modules/workspace/__tests__/workspace.routes.download.test.ts +6 -6
- package/src/modules/workspace/__tests__/workspace.routes.file-writes.test.ts +6 -6
- package/src/modules/workspace/__tests__/workspace.routes.folder-zip-access.test.ts +4 -4
- package/src/modules/workspace/__tests__/workspace.routes.folders.test.ts +6 -6
- package/src/modules/workspace/__tests__/workspace.routes.move-platform-files.test.ts +7 -7
- package/src/modules/workspace/__tests__/workspace.routes.read-gate.test.ts +13 -13
- package/src/modules/workspace/__tests__/workspace.service.any-workspace-credentials.test.ts +1 -1
- package/src/modules/workspace/__tests__/workspace.service.replaced-repository.test.ts +1 -1
- package/src/modules/workspace/__tests__/workspace.service.worktrees.test.ts +1 -1
- package/src/modules/workspace/__tests__/workspace.tools.agents-file.test.ts +5 -5
- package/src/modules/workspace/__tests__/workspace.tools.branch-errors.test.ts +5 -5
- package/src/modules/workspace/__tests__/workspace.tools.test.ts +27 -27
- package/src/modules/workspace/agent-access.gate.ts +5 -5
- package/src/modules/workspace/git-internals.middleware.ts +2 -2
- package/src/modules/workspace/kb-repository.ts +1 -1
- package/src/modules/workspace/startup/__tests__/kb-startup-runner.degraded.test.ts +1 -1
- package/src/modules/workspace/startup/__tests__/kb-startup-runner.test.ts +1 -1
- package/src/modules/workspace/startup/__tests__/kb-startup-runner.worktrees.test.ts +1 -1
- package/src/modules/workspace/startup/kb-git.ts +1 -1
- package/src/modules/workspace/startup/steps/__tests__/steps.test.ts +94 -70
- package/src/modules/workspace/startup/steps/seed-tree.ts +4 -5
- package/src/modules/workspace/startup/steps/template-files.step.ts +4 -5
- package/src/modules/workspace/startup/steps/template-source.ts +21 -6
- package/src/modules/workspace/workspace-tmp.ts +4 -4
- package/src/modules/workspace/workspace.routes.ts +26 -26
- package/src/modules/workspace/workspace.service.ts +11 -11
- package/src/modules/workspace/workspace.tools.ts +17 -10
- package/src/shared/__tests__/canonical-file-identity.test.ts +5 -5
- package/src/shared/__tests__/fire-and-forget.test.ts +39 -0
- package/src/shared/__tests__/http-errors.test.ts +4 -4
- package/src/shared/canonical-file-identity.ts +5 -5
- package/src/shared/domain-errors.ts +44 -44
- package/src/shared/fire-and-forget.ts +26 -0
- package/src/shared/git.contract.ts +1 -1
- package/src/shared/http-errors.ts +3 -3
- package/src/shared/kb-context.ts +130 -130
- package/src/shared/pending-proposals.ts +4 -4
- package/src/tenancy/__tests__/tenant-host.test.ts +372 -372
- package/src/tenancy/__tests__/tenant-runtime.test.ts +221 -221
- package/src/tenancy/__tests__/tenant-secrets.test.ts +35 -35
- package/src/tenancy/index.ts +35 -35
- package/src/tenancy/static-tenant-source.ts +1 -1
- package/src/tenancy/tenant-host.ts +261 -261
- package/src/tenancy/tenant-runtime.ts +208 -208
- package/src/tenancy/tenant-source.contract.ts +59 -59
- package/dist/modules/workflow/agent-tools/change-request-read-shape.d.ts.map +0 -1
- package/dist/modules/workflow/agent-tools/change-request-read-shape.js.map +0 -1
- package/dist/modules/workflow/agent-tools/change-request-read.tools.d.ts.map +0 -1
- package/dist/modules/workflow/agent-tools/change-request-read.tools.js.map +0 -1
- package/dist/modules/workflow/agent-tools/change-request-summary.d.ts.map +0 -1
- package/dist/modules/workflow/agent-tools/change-request-summary.js.map +0 -1
- package/dist/modules/workflow/agent-tools/workflow.tools.d.ts.map +0 -1
- package/dist/modules/workflow/agent-tools/workflow.tools.js.map +0 -1
- package/dist/modules/workflow/change-request-hooks.d.ts.map +0 -1
- package/dist/modules/workflow/change-request-hooks.js.map +0 -1
- package/dist/modules/workflow/event-bus.d.ts.map +0 -1
- package/dist/modules/workflow/event-bus.js.map +0 -1
- package/dist/modules/workflow/events-delivery.d.ts.map +0 -1
- package/dist/modules/workflow/events-delivery.js.map +0 -1
- package/dist/modules/workflow/events.routes.d.ts.map +0 -1
- package/dist/modules/workflow/events.routes.js.map +0 -1
- package/dist/modules/workflow/file-lock.service.d.ts.map +0 -1
- package/dist/modules/workflow/file-lock.service.js.map +0 -1
- package/dist/modules/workflow/git/change-request-link.d.ts.map +0 -1
- package/dist/modules/workflow/git/change-request-link.js.map +0 -1
- package/dist/modules/workflow/git/git-version.d.ts.map +0 -1
- package/dist/modules/workflow/git/git-version.js.map +0 -1
- package/dist/modules/workflow/git/git.service.d.ts.map +0 -1
- package/dist/modules/workflow/git/git.service.js.map +0 -1
- package/dist/modules/workflow/git/merge-commit.d.ts.map +0 -1
- package/dist/modules/workflow/git/merge-commit.js.map +0 -1
- package/dist/modules/workflow/git/node-git-runner.d.ts.map +0 -1
- package/dist/modules/workflow/git/node-git-runner.js.map +0 -1
- package/dist/modules/workflow/git/pull-request.service.d.ts.map +0 -1
- package/dist/modules/workflow/git/pull-request.service.js.map +0 -1
- package/dist/modules/workflow/index.d.ts.map +0 -1
- package/dist/modules/workflow/index.js.map +0 -1
- package/dist/modules/workflow/pending-commits.service.d.ts.map +0 -1
- package/dist/modules/workflow/pending-commits.service.js.map +0 -1
- package/dist/modules/workflow/pending-commits.worker.d.ts.map +0 -1
- package/dist/modules/workflow/pending-commits.worker.js.map +0 -1
- package/dist/modules/workflow/read-on-branch.d.ts.map +0 -1
- package/dist/modules/workflow/read-on-branch.js.map +0 -1
- package/dist/modules/workflow/recovery-bot.d.ts.map +0 -1
- package/dist/modules/workflow/recovery-bot.js.map +0 -1
- package/dist/modules/workflow/review-workflow/approval-lock.d.ts.map +0 -1
- package/dist/modules/workflow/review-workflow/approval-lock.js.map +0 -1
- package/dist/modules/workflow/review-workflow/review-workflow.interface.d.ts.map +0 -1
- package/dist/modules/workflow/review-workflow/review-workflow.interface.js +0 -2
- package/dist/modules/workflow/review-workflow/review-workflow.interface.js.map +0 -1
- package/dist/modules/workflow/review-workflow/review-workflow.service.d.ts.map +0 -1
- package/dist/modules/workflow/review-workflow/review-workflow.service.js.map +0 -1
- package/dist/modules/workflow/sanitize-error.d.ts.map +0 -1
- package/dist/modules/workflow/sanitize-error.js.map +0 -1
- package/dist/modules/workflow/workflow-hooks.d.ts.map +0 -1
- package/dist/modules/workflow/workflow-hooks.js.map +0 -1
- package/dist/modules/workflow/workflow.routes.d.ts.map +0 -1
- package/dist/modules/workflow/workflow.routes.js.map +0 -1
- package/dist/modules/workflow/workflow.service.d.ts.map +0 -1
- package/dist/modules/workflow/workflow.service.js.map +0 -1
- package/src/modules/mcp/__tests__/read-only-hint.e2e.test.ts +0 -213
- /package/dist/modules/{workflow → version-control}/agent-tools/change-request-read-shape.d.ts +0 -0
- /package/dist/modules/{workflow → version-control}/agent-tools/change-request-read-shape.js +0 -0
- /package/dist/modules/{workflow → version-control}/agent-tools/change-request-read.tools.d.ts +0 -0
- /package/dist/modules/{workflow → version-control}/agent-tools/change-request-summary.d.ts +0 -0
- /package/dist/modules/{workflow → version-control}/agent-tools/change-request-summary.js +0 -0
- /package/dist/modules/{workflow → version-control}/change-request-hooks.js +0 -0
- /package/dist/modules/{workflow/review-workflow → version-control/change-request-review}/approval-lock.d.ts +0 -0
- /package/dist/modules/{workflow/review-workflow → version-control/change-request-review}/approval-lock.js +0 -0
- /package/dist/modules/{workflow → version-control}/events-delivery.js +0 -0
- /package/dist/modules/{workflow → version-control}/events.routes.js +0 -0
- /package/dist/modules/{workflow → version-control}/git/change-request-link.d.ts +0 -0
- /package/dist/modules/{workflow → version-control}/git/change-request-link.js +0 -0
- /package/dist/modules/{workflow → version-control}/git/git-version.d.ts +0 -0
- /package/dist/modules/{workflow → version-control}/git/git-version.js +0 -0
- /package/dist/modules/{workflow → version-control}/git/merge-commit.d.ts +0 -0
- /package/dist/modules/{workflow → version-control}/git/merge-commit.js +0 -0
- /package/dist/modules/{workflow → version-control}/git/node-git-runner.d.ts +0 -0
- /package/dist/modules/{workflow → version-control}/recovery-bot.d.ts +0 -0
- /package/dist/modules/{workflow → version-control}/recovery-bot.js +0 -0
- /package/dist/modules/{workflow → version-control}/sanitize-error.d.ts +0 -0
- /package/dist/modules/{workflow → version-control}/sanitize-error.js +0 -0
- /package/src/modules/{workflow → version-control}/__tests__/change-request-hooks.test.ts +0 -0
- /package/src/modules/{workflow → version-control}/__tests__/fake-file-lock-db.ts +0 -0
- /package/src/modules/{workflow → version-control}/__tests__/pending-commits.canonical.test.ts +0 -0
- /package/src/modules/{workflow → version-control}/__tests__/pending-commits.enqueue-if-absent.test.ts +0 -0
- /package/src/modules/{workflow → version-control}/__tests__/pending-commits.repository-replaced.test.ts +0 -0
- /package/src/modules/{workflow → version-control}/__tests__/sanitize-error.test.ts +0 -0
- /package/src/modules/{workflow → version-control}/agent-tools/__tests__/change-request-read-shape.test.ts +0 -0
- /package/src/modules/{workflow → version-control}/agent-tools/change-request-read-shape.ts +0 -0
- /package/src/modules/{workflow → version-control}/agent-tools/change-request-summary.ts +0 -0
- /package/src/modules/{workflow/review-workflow → version-control/change-request-review}/approval-lock.ts +0 -0
- /package/src/modules/{workflow → version-control}/git/__tests__/branchAuthor.test.ts +0 -0
- /package/src/modules/{workflow → version-control}/git/__tests__/change-request-link.test.ts +0 -0
- /package/src/modules/{workflow → version-control}/git/__tests__/git-version.failure.test.ts +0 -0
- /package/src/modules/{workflow → version-control}/git/__tests__/git-version.test.ts +0 -0
- /package/src/modules/{workflow → version-control}/git/__tests__/node-git-runner.test.ts +0 -0
- /package/src/modules/{workflow → version-control}/git/__tests__/pull-request.service.getPrDetail.test.ts +0 -0
- /package/src/modules/{workflow → version-control}/git/__tests__/pull-request.service.placeholder.test.ts +0 -0
- /package/src/modules/{workflow → version-control}/git/__tests__/pull-request.service.viewer-can-cancel.test.ts +0 -0
- /package/src/modules/{workflow → version-control}/git/__tests__/pull-request.service.viewer-can-delete.test.ts +0 -0
- /package/src/modules/{workflow → version-control}/git/__tests__/pull-request.service.viewer-can-update.test.ts +0 -0
- /package/src/modules/{workflow → version-control}/git/change-request-link.ts +0 -0
- /package/src/modules/{workflow → version-control}/git/git-version.ts +0 -0
- /package/src/modules/{workflow → version-control}/git/merge-commit.ts +0 -0
- /package/src/modules/{workflow → version-control}/recovery-bot.ts +0 -0
- /package/src/modules/{workflow → version-control}/sanitize-error.ts +0 -0
|
@@ -2,15 +2,11 @@ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
|
2
2
|
import fs from 'node:fs/promises';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import os from 'node:os';
|
|
5
|
-
import type { AuthUser,
|
|
5
|
+
import type { AuthUser, IVersionControlService } from '@bevel-software/platform-shared';
|
|
6
6
|
import { LocalFilesystem } from '@mastra/core/workspace';
|
|
7
7
|
import { LockingFilesystem } from '../locking-filesystem.js';
|
|
8
|
-
import { PushNeedsAgentResolutionError,
|
|
9
|
-
import {
|
|
10
|
-
makeAgentRolesYamlWriteValidator,
|
|
11
|
-
RolesYamlInvalidError,
|
|
12
|
-
RolesYamlNewRoleError,
|
|
13
|
-
} from '../../access-model/roles-yaml-guard.js';
|
|
8
|
+
import { PushNeedsAgentResolutionError, VersionControlValidationError } from '../../../shared/domain-errors.js';
|
|
9
|
+
import { makeAgentRolesYamlWriteValidator } from '../../access-model/index.js';
|
|
14
10
|
|
|
15
11
|
/** The clone folder at the workspace root: every path the filesystem mutates lives under it. */
|
|
16
12
|
const KB = 'knowledge-base';
|
|
@@ -21,7 +17,7 @@ const USER: AuthUser = {
|
|
|
21
17
|
name: 'Alice',
|
|
22
18
|
};
|
|
23
19
|
|
|
24
|
-
function
|
|
20
|
+
function makeVersionControl(): IVersionControlService {
|
|
25
21
|
return {
|
|
26
22
|
acquireLock: vi.fn().mockResolvedValue({
|
|
27
23
|
acquired: true,
|
|
@@ -42,7 +38,7 @@ function makeWorkflow(): IWorkflowService {
|
|
|
42
38
|
getLock: vi.fn(),
|
|
43
39
|
commitFileWhileLocked: vi.fn(),
|
|
44
40
|
commitChanges: vi.fn().mockResolvedValue({}),
|
|
45
|
-
} as unknown as
|
|
41
|
+
} as unknown as IVersionControlService;
|
|
46
42
|
}
|
|
47
43
|
|
|
48
44
|
async function mkTmpRoot(): Promise<string> {
|
|
@@ -61,29 +57,29 @@ describe('LockingFilesystem — every mutating op runs acquire → super → rel
|
|
|
61
57
|
});
|
|
62
58
|
|
|
63
59
|
it('writeFile acquires, writes through to disk, then releases', async () => {
|
|
64
|
-
const
|
|
60
|
+
const versionControl = makeVersionControl();
|
|
65
61
|
const fsLayer = new LockingFilesystem(
|
|
66
62
|
{ basePath: root, contained: true },
|
|
67
|
-
{
|
|
63
|
+
{ versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB },
|
|
68
64
|
);
|
|
69
65
|
await fsLayer.writeFile('knowledge-base/Knowledge/Foo.md', 'hello\n');
|
|
70
66
|
|
|
71
|
-
expect(
|
|
67
|
+
expect(versionControl.acquireLock).toHaveBeenCalledWith(
|
|
72
68
|
'ws-feat',
|
|
73
69
|
'feat',
|
|
74
70
|
'knowledge-base/Knowledge/Foo.md',
|
|
75
71
|
USER,
|
|
76
72
|
);
|
|
77
|
-
expect(
|
|
73
|
+
expect(versionControl.releaseLock).toHaveBeenCalledWith(
|
|
78
74
|
'ws-feat',
|
|
79
75
|
'feat',
|
|
80
76
|
'knowledge-base/Knowledge/Foo.md',
|
|
81
77
|
USER,
|
|
82
78
|
);
|
|
83
79
|
// Acquire fires before release.
|
|
84
|
-
const acquireOrder = (
|
|
80
|
+
const acquireOrder = (versionControl.acquireLock as ReturnType<typeof vi.fn>).mock
|
|
85
81
|
.invocationCallOrder[0];
|
|
86
|
-
const releaseOrder = (
|
|
82
|
+
const releaseOrder = (versionControl.releaseLock as ReturnType<typeof vi.fn>).mock
|
|
87
83
|
.invocationCallOrder[0];
|
|
88
84
|
expect(acquireOrder).toBeLessThan(releaseOrder);
|
|
89
85
|
// The bytes actually landed on disk.
|
|
@@ -94,33 +90,33 @@ describe('LockingFilesystem — every mutating op runs acquire → super → rel
|
|
|
94
90
|
const filePath = path.join(root, 'knowledge-base/a.md');
|
|
95
91
|
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
96
92
|
await fs.writeFile(filePath, 'x', 'utf-8');
|
|
97
|
-
const
|
|
93
|
+
const versionControl = makeVersionControl();
|
|
98
94
|
const fsLayer = new LockingFilesystem(
|
|
99
95
|
{ basePath: root, contained: true },
|
|
100
|
-
{
|
|
96
|
+
{ versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB },
|
|
101
97
|
);
|
|
102
98
|
await fsLayer.deleteFile('knowledge-base/a.md');
|
|
103
|
-
expect(
|
|
104
|
-
expect(
|
|
99
|
+
expect(versionControl.acquireLock).toHaveBeenCalledWith('ws-feat', 'feat', 'knowledge-base/a.md', USER);
|
|
100
|
+
expect(versionControl.releaseLock).toHaveBeenCalledWith('ws-feat', 'feat', 'knowledge-base/a.md', USER);
|
|
105
101
|
await expect(fs.access(filePath)).rejects.toBeDefined();
|
|
106
102
|
});
|
|
107
103
|
|
|
108
104
|
it('mkdir drops a .gitkeep through the lock-aware writeFile so the empty folder is committed', async () => {
|
|
109
|
-
const
|
|
105
|
+
const versionControl = makeVersionControl();
|
|
110
106
|
const fsLayer = new LockingFilesystem(
|
|
111
107
|
{ basePath: root, contained: true },
|
|
112
|
-
{
|
|
108
|
+
{ versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB },
|
|
113
109
|
);
|
|
114
110
|
await fsLayer.mkdir('knowledge-base/empty');
|
|
115
111
|
// .gitkeep got written through the lock path. The lock is on the
|
|
116
112
|
// .gitkeep path (that's the file the commit attaches to).
|
|
117
|
-
expect(
|
|
113
|
+
expect(versionControl.acquireLock).toHaveBeenCalledWith(
|
|
118
114
|
'ws-feat',
|
|
119
115
|
'feat',
|
|
120
116
|
'knowledge-base/empty/.gitkeep',
|
|
121
117
|
USER,
|
|
122
118
|
);
|
|
123
|
-
expect(
|
|
119
|
+
expect(versionControl.releaseLock).toHaveBeenCalledWith(
|
|
124
120
|
'ws-feat',
|
|
125
121
|
'feat',
|
|
126
122
|
'knowledge-base/empty/.gitkeep',
|
|
@@ -132,37 +128,37 @@ describe('LockingFilesystem — every mutating op runs acquire → super → rel
|
|
|
132
128
|
it('mkdir of an already-populated dir does NOT drop a .gitkeep', async () => {
|
|
133
129
|
await fs.mkdir(path.join(root, 'knowledge-base/has-content'), { recursive: true });
|
|
134
130
|
await fs.writeFile(path.join(root, 'knowledge-base/has-content/real.md'), 'hi', 'utf-8');
|
|
135
|
-
const
|
|
131
|
+
const versionControl = makeVersionControl();
|
|
136
132
|
const fsLayer = new LockingFilesystem(
|
|
137
133
|
{ basePath: root, contained: true },
|
|
138
|
-
{
|
|
134
|
+
{ versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB },
|
|
139
135
|
);
|
|
140
136
|
await fsLayer.mkdir('knowledge-base/has-content', { recursive: true });
|
|
141
|
-
expect(
|
|
142
|
-
expect(
|
|
137
|
+
expect(versionControl.acquireLock).not.toHaveBeenCalled();
|
|
138
|
+
expect(versionControl.releaseLock).not.toHaveBeenCalled();
|
|
143
139
|
// The existing file is untouched.
|
|
144
140
|
expect(await fs.readFile(path.join(root, 'knowledge-base/has-content/real.md'), 'utf-8')).toBe('hi');
|
|
145
141
|
});
|
|
146
142
|
|
|
147
143
|
it('rmdir is refused with a clear error — one-change-per-file invariant', async () => {
|
|
148
144
|
await fs.mkdir(path.join(root, 'knowledge-base/doomed'), { recursive: true });
|
|
149
|
-
const
|
|
145
|
+
const versionControl = makeVersionControl();
|
|
150
146
|
const fsLayer = new LockingFilesystem(
|
|
151
147
|
{ basePath: root, contained: true },
|
|
152
|
-
{
|
|
148
|
+
{ versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB },
|
|
153
149
|
);
|
|
154
150
|
await expect(fsLayer.rmdir('knowledge-base/doomed')).rejects.toThrow(
|
|
155
151
|
/Recursive directory removal is not supported/i,
|
|
156
152
|
);
|
|
157
|
-
expect(
|
|
153
|
+
expect(versionControl.acquireLock).not.toHaveBeenCalled();
|
|
158
154
|
});
|
|
159
155
|
|
|
160
156
|
it(
|
|
161
157
|
'retries acquire on contention then surfaces a skip error after 3 attempts',
|
|
162
158
|
async () => {
|
|
163
159
|
// 3 attempts × 2s ≈ 6s — bump vitest's default timeout for this one test.
|
|
164
|
-
const
|
|
165
|
-
(
|
|
160
|
+
const versionControl = makeVersionControl();
|
|
161
|
+
(versionControl.acquireLock as ReturnType<typeof vi.fn>).mockResolvedValue({
|
|
166
162
|
acquired: false,
|
|
167
163
|
lock: {
|
|
168
164
|
branch: 'feat',
|
|
@@ -176,23 +172,23 @@ describe('LockingFilesystem — every mutating op runs acquire → super → rel
|
|
|
176
172
|
});
|
|
177
173
|
const fsLayer = new LockingFilesystem(
|
|
178
174
|
{ basePath: root, contained: true },
|
|
179
|
-
{
|
|
175
|
+
{ versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB },
|
|
180
176
|
);
|
|
181
177
|
|
|
182
178
|
await expect(fsLayer.writeFile('knowledge-base/Locked.md', 'x')).rejects.toThrow(
|
|
183
179
|
/Skipped editing "knowledge-base\/Locked\.md" — locked by Bob/,
|
|
184
180
|
);
|
|
185
|
-
expect(
|
|
186
|
-
expect(
|
|
181
|
+
expect(versionControl.acquireLock).toHaveBeenCalledTimes(3);
|
|
182
|
+
expect(versionControl.releaseLock).not.toHaveBeenCalled();
|
|
187
183
|
},
|
|
188
184
|
10_000,
|
|
189
185
|
);
|
|
190
186
|
|
|
191
187
|
it('drops the lock without committing when the underlying write throws', async () => {
|
|
192
|
-
const
|
|
188
|
+
const versionControl = makeVersionControl();
|
|
193
189
|
const fsLayer = new LockingFilesystem(
|
|
194
190
|
{ basePath: root, contained: true },
|
|
195
|
-
{
|
|
191
|
+
{ versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB },
|
|
196
192
|
);
|
|
197
193
|
// The underlying write dies (disk full, permissions, whatever). The lock
|
|
198
194
|
// must still go away so the next caller can edit a different file, but
|
|
@@ -207,13 +203,13 @@ describe('LockingFilesystem — every mutating op runs acquire → super → rel
|
|
|
207
203
|
} finally {
|
|
208
204
|
spy.mockRestore();
|
|
209
205
|
}
|
|
210
|
-
expect(
|
|
211
|
-
expect(
|
|
212
|
-
expect(
|
|
206
|
+
expect(versionControl.acquireLock).toHaveBeenCalled();
|
|
207
|
+
expect(versionControl.releaseLockNoCommit).toHaveBeenCalled();
|
|
208
|
+
expect(versionControl.releaseLock).not.toHaveBeenCalled();
|
|
213
209
|
});
|
|
214
210
|
});
|
|
215
211
|
|
|
216
|
-
describe('LockingFilesystem.writeFiles — batch with deletes
|
|
212
|
+
describe('LockingFilesystem.writeFiles — batch with deletes', () => {
|
|
217
213
|
let root: string;
|
|
218
214
|
|
|
219
215
|
beforeEach(async () => {
|
|
@@ -224,20 +220,18 @@ describe('LockingFilesystem.writeFiles — batch with deletes + one batched chan
|
|
|
224
220
|
await fs.rm(root, { recursive: true, force: true });
|
|
225
221
|
});
|
|
226
222
|
|
|
227
|
-
it('locks writes AND deletes, applies both,
|
|
223
|
+
it('locks writes AND deletes, applies both, returns the commit', async () => {
|
|
228
224
|
await fs.mkdir(path.join(root, 'knowledge-base/Tools'), { recursive: true });
|
|
229
225
|
await fs.writeFile(path.join(root, 'knowledge-base/Tools/old.tool'), '{}');
|
|
230
226
|
|
|
231
|
-
const
|
|
232
|
-
const emitted: unknown[] = [];
|
|
227
|
+
const versionControl = makeVersionControl();
|
|
233
228
|
const fsLayer = new LockingFilesystem(
|
|
234
229
|
{ basePath: root, contained: true },
|
|
235
230
|
{
|
|
236
|
-
|
|
231
|
+
versionControl,
|
|
237
232
|
workspaceId: 'ws-feat',
|
|
238
233
|
branch: 'feat',
|
|
239
234
|
user: USER, kbDirName: KB,
|
|
240
|
-
fileChanges: { emit: (c: unknown) => emitted.push(c) } as never,
|
|
241
235
|
},
|
|
242
236
|
);
|
|
243
237
|
|
|
@@ -248,36 +242,26 @@ describe('LockingFilesystem.writeFiles — batch with deletes + one batched chan
|
|
|
248
242
|
);
|
|
249
243
|
|
|
250
244
|
// Both paths locked; both mutations landed; commit returned.
|
|
251
|
-
expect(
|
|
252
|
-
expect(
|
|
245
|
+
expect(versionControl.acquireLock).toHaveBeenCalledWith('ws-feat', 'feat', 'knowledge-base/Tools/new.tool', USER);
|
|
246
|
+
expect(versionControl.acquireLock).toHaveBeenCalledWith('ws-feat', 'feat', 'knowledge-base/Tools/old.tool', USER);
|
|
253
247
|
expect(await fs.readFile(path.join(root, 'knowledge-base/Tools/new.tool'), 'utf-8')).toContain('http');
|
|
254
248
|
await expect(fs.access(path.join(root, 'knowledge-base/Tools/old.tool'))).rejects.toThrow();
|
|
255
249
|
expect(change).toEqual({});
|
|
256
|
-
// ONE event carrying the whole batch (sorted), attributed to the user.
|
|
257
|
-
expect(emitted).toHaveLength(1);
|
|
258
|
-
expect(emitted[0]).toMatchObject({
|
|
259
|
-
workspaceId: 'ws-feat',
|
|
260
|
-
branch: 'feat',
|
|
261
|
-
paths: ['knowledge-base/Tools/new.tool', 'knowledge-base/Tools/old.tool'],
|
|
262
|
-
byUser: USER,
|
|
263
|
-
});
|
|
264
250
|
});
|
|
265
251
|
|
|
266
|
-
it('a failing commit releases every lock WITHOUT committing
|
|
252
|
+
it('a failing commit releases every lock WITHOUT committing', async () => {
|
|
267
253
|
await fs.mkdir(path.join(root, 'knowledge-base/Tools'), { recursive: true });
|
|
268
254
|
await fs.writeFile(path.join(root, 'knowledge-base/Tools/old.tool'), '{}');
|
|
269
255
|
|
|
270
|
-
const
|
|
271
|
-
(
|
|
272
|
-
const emitted: unknown[] = [];
|
|
256
|
+
const versionControl = makeVersionControl();
|
|
257
|
+
(versionControl.commitChanges as ReturnType<typeof vi.fn>).mockRejectedValue(new Error('push exploded'));
|
|
273
258
|
const fsLayer = new LockingFilesystem(
|
|
274
259
|
{ basePath: root, contained: true },
|
|
275
260
|
{
|
|
276
|
-
|
|
261
|
+
versionControl,
|
|
277
262
|
workspaceId: 'ws-feat',
|
|
278
263
|
branch: 'feat',
|
|
279
264
|
user: USER, kbDirName: KB,
|
|
280
|
-
fileChanges: { emit: (c: unknown) => emitted.push(c) } as never,
|
|
281
265
|
},
|
|
282
266
|
);
|
|
283
267
|
|
|
@@ -291,12 +275,11 @@ describe('LockingFilesystem.writeFiles — batch with deletes + one batched chan
|
|
|
291
275
|
|
|
292
276
|
// Every acquired path goes through the NO-COMMIT release — which is what
|
|
293
277
|
// discards the uncommitted bytes (write reverted, delete restored) in the
|
|
294
|
-
// real
|
|
295
|
-
// never run
|
|
296
|
-
expect(
|
|
297
|
-
expect(
|
|
298
|
-
expect(
|
|
299
|
-
expect(emitted).toHaveLength(0);
|
|
278
|
+
// real VersionControlService via git.discardPath. The committing release must
|
|
279
|
+
// never run.
|
|
280
|
+
expect(versionControl.releaseLockNoCommit).toHaveBeenCalledWith('ws-feat', 'feat', 'knowledge-base/Tools/new.tool', USER);
|
|
281
|
+
expect(versionControl.releaseLockNoCommit).toHaveBeenCalledWith('ws-feat', 'feat', 'knowledge-base/Tools/old.tool', USER);
|
|
282
|
+
expect(versionControl.releaseLock).not.toHaveBeenCalled();
|
|
300
283
|
});
|
|
301
284
|
|
|
302
285
|
it('a POST-commit push failure releases WITH commit-on-release (arms the worker retry), never the discard path', async () => {
|
|
@@ -307,19 +290,17 @@ describe('LockingFilesystem.writeFiles — batch with deletes + one batched chan
|
|
|
307
290
|
// forever. The batch must releaseLock (the enqueued release commit no-ops
|
|
308
291
|
// on the clean tree and the worker's unpushed-commits check re-runs the
|
|
309
292
|
// push ladder) and still propagate the error.
|
|
310
|
-
const
|
|
311
|
-
(
|
|
293
|
+
const versionControl = makeVersionControl();
|
|
294
|
+
(versionControl.commitChanges as ReturnType<typeof vi.fn>).mockRejectedValue(
|
|
312
295
|
new PushNeedsAgentResolutionError('feat', 'knowledge-base/A.md', 'non-fast-forward', 'rebase failed'),
|
|
313
296
|
);
|
|
314
|
-
const emitted: unknown[] = [];
|
|
315
297
|
const fsLayer = new LockingFilesystem(
|
|
316
298
|
{ basePath: root, contained: true },
|
|
317
299
|
{
|
|
318
|
-
|
|
300
|
+
versionControl,
|
|
319
301
|
workspaceId: 'ws-feat',
|
|
320
302
|
branch: 'feat',
|
|
321
303
|
user: USER, kbDirName: KB,
|
|
322
|
-
fileChanges: { emit: (c: unknown) => emitted.push(c) } as never,
|
|
323
304
|
},
|
|
324
305
|
);
|
|
325
306
|
|
|
@@ -327,27 +308,8 @@ describe('LockingFilesystem.writeFiles — batch with deletes + one batched chan
|
|
|
327
308
|
fsLayer.writeFiles([{ path: 'knowledge-base/A.md', content: 'x' }], 'batch'),
|
|
328
309
|
).rejects.toBeInstanceOf(PushNeedsAgentResolutionError);
|
|
329
310
|
|
|
330
|
-
expect(
|
|
331
|
-
expect(
|
|
332
|
-
expect(emitted).toHaveLength(0);
|
|
333
|
-
});
|
|
334
|
-
|
|
335
|
-
it('emits nothing on a no-op commit (clean tree)', async () => {
|
|
336
|
-
const workflow = makeWorkflow();
|
|
337
|
-
(workflow.commitChanges as ReturnType<typeof vi.fn>).mockResolvedValue(null);
|
|
338
|
-
const emitted: unknown[] = [];
|
|
339
|
-
const fsLayer = new LockingFilesystem(
|
|
340
|
-
{ basePath: root, contained: true },
|
|
341
|
-
{
|
|
342
|
-
workflow,
|
|
343
|
-
workspaceId: 'ws-feat',
|
|
344
|
-
branch: 'feat',
|
|
345
|
-
user: USER, kbDirName: KB,
|
|
346
|
-
fileChanges: { emit: (c: unknown) => emitted.push(c) } as never,
|
|
347
|
-
},
|
|
348
|
-
);
|
|
349
|
-
await fsLayer.writeFiles([{ path: 'knowledge-base/A.md', content: 'same' }], 'noop');
|
|
350
|
-
expect(emitted).toHaveLength(0);
|
|
311
|
+
expect(versionControl.releaseLock).toHaveBeenCalledWith('ws-feat', 'feat', 'knowledge-base/A.md', USER);
|
|
312
|
+
expect(versionControl.releaseLockNoCommit).not.toHaveBeenCalled();
|
|
351
313
|
});
|
|
352
314
|
});
|
|
353
315
|
|
|
@@ -362,24 +324,24 @@ describe('LockingFilesystem — the caller judges under the lock', () => {
|
|
|
362
324
|
await fs.rm(root, { recursive: true, force: true });
|
|
363
325
|
});
|
|
364
326
|
|
|
365
|
-
const layer = (
|
|
327
|
+
const layer = (versionControl: IVersionControlService): LockingFilesystem =>
|
|
366
328
|
new LockingFilesystem(
|
|
367
329
|
{ basePath: root, contained: true },
|
|
368
|
-
{
|
|
330
|
+
{ versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB },
|
|
369
331
|
);
|
|
370
332
|
|
|
371
333
|
it('writeFile runs the check with the lock HELD and before any byte lands', async () => {
|
|
372
334
|
await fs.mkdir(path.join(root, 'knowledge-base'), { recursive: true });
|
|
373
335
|
await fs.writeFile(path.join(root, 'knowledge-base/Foo.md'), 'before\n');
|
|
374
336
|
|
|
375
|
-
const
|
|
337
|
+
const versionControl = makeVersionControl();
|
|
376
338
|
const order: string[] = [];
|
|
377
|
-
(
|
|
339
|
+
(versionControl.acquireLock as ReturnType<typeof vi.fn>).mockImplementation(async () => {
|
|
378
340
|
order.push('acquire');
|
|
379
341
|
return { acquired: true, lock: { holderName: 'Alice' } };
|
|
380
342
|
});
|
|
381
343
|
|
|
382
|
-
await layer(
|
|
344
|
+
await layer(versionControl).writeFile('knowledge-base/Foo.md', 'after\n', undefined, async () => {
|
|
383
345
|
order.push('check');
|
|
384
346
|
// The check reads the state the write is about to replace.
|
|
385
347
|
expect(await fs.readFile(path.join(root, 'knowledge-base/Foo.md'), 'utf-8')).toBe('before\n');
|
|
@@ -393,10 +355,10 @@ describe('LockingFilesystem — the caller judges under the lock', () => {
|
|
|
393
355
|
await fs.mkdir(path.join(root, 'knowledge-base'), { recursive: true });
|
|
394
356
|
await fs.writeFile(path.join(root, 'knowledge-base/Foo.md'), 'theirs\n');
|
|
395
357
|
|
|
396
|
-
const
|
|
358
|
+
const versionControl = makeVersionControl();
|
|
397
359
|
const refusal = new Error('already exists');
|
|
398
360
|
await expect(
|
|
399
|
-
layer(
|
|
361
|
+
layer(versionControl).writeFile('knowledge-base/Foo.md', 'mine\n', undefined, () => {
|
|
400
362
|
throw refusal;
|
|
401
363
|
}),
|
|
402
364
|
).rejects.toBe(refusal);
|
|
@@ -404,30 +366,30 @@ describe('LockingFilesystem — the caller judges under the lock', () => {
|
|
|
404
366
|
expect(await fs.readFile(path.join(root, 'knowledge-base/Foo.md'), 'utf-8')).toBe('theirs\n');
|
|
405
367
|
// Untouched, never no-commit: a discard would reset the path to HEAD and
|
|
406
368
|
// destroy whatever save left those bytes there.
|
|
407
|
-
expect(
|
|
408
|
-
expect(
|
|
409
|
-
expect(
|
|
369
|
+
expect(versionControl.releaseLockUntouched).toHaveBeenCalledWith('ws-feat', 'feat', 'knowledge-base/Foo.md', USER);
|
|
370
|
+
expect(versionControl.releaseLockNoCommit).not.toHaveBeenCalled();
|
|
371
|
+
expect(versionControl.releaseLock).not.toHaveBeenCalled();
|
|
410
372
|
});
|
|
411
373
|
|
|
412
374
|
it('rewriteFile reads, computes and writes with the lock HELD, from the bytes on disk at that moment', async () => {
|
|
413
375
|
await fs.mkdir(path.join(root, 'knowledge-base'), { recursive: true });
|
|
414
376
|
await fs.writeFile(path.join(root, 'knowledge-base/Foo.md'), 'owner: \n');
|
|
415
377
|
|
|
416
|
-
const
|
|
378
|
+
const versionControl = makeVersionControl();
|
|
417
379
|
const order: string[] = [];
|
|
418
|
-
(
|
|
380
|
+
(versionControl.acquireLock as ReturnType<typeof vi.fn>).mockImplementation(async () => {
|
|
419
381
|
order.push('acquire');
|
|
420
382
|
// Another writer lands while this call waits for the lock.
|
|
421
383
|
await fs.writeFile(path.join(root, 'knowledge-base/Foo.md'), 'owner: alice\n');
|
|
422
384
|
return { acquired: true, lock: { holderName: 'Alice' } };
|
|
423
385
|
});
|
|
424
|
-
(
|
|
386
|
+
(versionControl.releaseLock as ReturnType<typeof vi.fn>).mockImplementation(async () => {
|
|
425
387
|
order.push('release');
|
|
426
388
|
return null;
|
|
427
389
|
});
|
|
428
390
|
|
|
429
391
|
let seen = '';
|
|
430
|
-
await layer(
|
|
392
|
+
await layer(versionControl).rewriteFile('knowledge-base/Foo.md', (current) => {
|
|
431
393
|
order.push('rewrite');
|
|
432
394
|
seen = current!.toString('utf8');
|
|
433
395
|
return `${seen}note: kept\n`;
|
|
@@ -437,32 +399,32 @@ describe('LockingFilesystem — the caller judges under the lock', () => {
|
|
|
437
399
|
// It was handed what the other writer left, not what was there when the call began.
|
|
438
400
|
expect(seen).toBe('owner: alice\n');
|
|
439
401
|
expect(await fs.readFile(path.join(root, 'knowledge-base/Foo.md'), 'utf-8')).toBe('owner: alice\nnote: kept\n');
|
|
440
|
-
expect(
|
|
402
|
+
expect(versionControl.releaseLock).toHaveBeenCalledWith('ws-feat', 'feat', 'knowledge-base/Foo.md', USER);
|
|
441
403
|
});
|
|
442
404
|
|
|
443
405
|
it('a rewriteFile that throws writes nothing, releases UNTOUCHED, and gives the caller its own error', async () => {
|
|
444
406
|
await fs.mkdir(path.join(root, 'knowledge-base'), { recursive: true });
|
|
445
407
|
await fs.writeFile(path.join(root, 'knowledge-base/Foo.md'), 'theirs\n');
|
|
446
408
|
|
|
447
|
-
const
|
|
409
|
+
const versionControl = makeVersionControl();
|
|
448
410
|
const refusal = new Error('old text is gone');
|
|
449
411
|
await expect(
|
|
450
|
-
layer(
|
|
412
|
+
layer(versionControl).rewriteFile('knowledge-base/Foo.md', () => {
|
|
451
413
|
throw refusal;
|
|
452
414
|
}),
|
|
453
415
|
).rejects.toBe(refusal);
|
|
454
416
|
|
|
455
417
|
expect(await fs.readFile(path.join(root, 'knowledge-base/Foo.md'), 'utf-8')).toBe('theirs\n');
|
|
456
|
-
expect(
|
|
457
|
-
expect(
|
|
458
|
-
expect(
|
|
418
|
+
expect(versionControl.releaseLockUntouched).toHaveBeenCalledWith('ws-feat', 'feat', 'knowledge-base/Foo.md', USER);
|
|
419
|
+
expect(versionControl.releaseLockNoCommit).not.toHaveBeenCalled();
|
|
420
|
+
expect(versionControl.releaseLock).not.toHaveBeenCalled();
|
|
459
421
|
});
|
|
460
422
|
|
|
461
423
|
it('rewriteFile hands over null when nothing is at the path', async () => {
|
|
462
424
|
await fs.mkdir(path.join(root, 'knowledge-base'), { recursive: true });
|
|
463
|
-
const
|
|
425
|
+
const versionControl = makeVersionControl();
|
|
464
426
|
let seen: Buffer | null | undefined;
|
|
465
|
-
await layer(
|
|
427
|
+
await layer(versionControl).rewriteFile('knowledge-base/New.md', (current) => {
|
|
466
428
|
seen = current;
|
|
467
429
|
return 'made\n';
|
|
468
430
|
});
|
|
@@ -473,13 +435,13 @@ describe('LockingFilesystem — the caller judges under the lock', () => {
|
|
|
473
435
|
it('rewriteFile judges the bytes it is about to write with the pre-disk validator, under the lock', async () => {
|
|
474
436
|
await fs.mkdir(path.join(root, 'knowledge-base'), { recursive: true });
|
|
475
437
|
await fs.writeFile(path.join(root, 'knowledge-base/Foo.md'), 'ok\n');
|
|
476
|
-
const
|
|
438
|
+
const versionControl = makeVersionControl();
|
|
477
439
|
const refusal = new Error('refused by the validator');
|
|
478
440
|
const judged: string[] = [];
|
|
479
441
|
const guarded = new LockingFilesystem(
|
|
480
442
|
{ basePath: root, contained: true },
|
|
481
443
|
{
|
|
482
|
-
|
|
444
|
+
versionControl,
|
|
483
445
|
workspaceId: 'ws-feat',
|
|
484
446
|
branch: 'feat',
|
|
485
447
|
user: USER,
|
|
@@ -493,19 +455,19 @@ describe('LockingFilesystem — the caller judges under the lock', () => {
|
|
|
493
455
|
await expect(guarded.rewriteFile('knowledge-base/Foo.md', () => 'bad\n')).rejects.toBe(refusal);
|
|
494
456
|
expect(judged).toEqual(['bad\n']);
|
|
495
457
|
expect(await fs.readFile(path.join(root, 'knowledge-base/Foo.md'), 'utf-8')).toBe('ok\n');
|
|
496
|
-
expect(
|
|
458
|
+
expect(versionControl.releaseLockUntouched).toHaveBeenCalled();
|
|
497
459
|
});
|
|
498
460
|
|
|
499
461
|
it('rewriteFile refuses a path outside the repository before any lock', async () => {
|
|
500
|
-
const
|
|
501
|
-
await expect(layer(
|
|
502
|
-
expect(
|
|
462
|
+
const versionControl = makeVersionControl();
|
|
463
|
+
await expect(layer(versionControl).rewriteFile('elsewhere/Foo.md', () => 'x')).rejects.toThrow();
|
|
464
|
+
expect(versionControl.acquireLock).not.toHaveBeenCalled();
|
|
503
465
|
});
|
|
504
466
|
|
|
505
467
|
it('writeFiles runs its check once EVERY lock is held, and lands only what the check keeps', async () => {
|
|
506
|
-
const
|
|
468
|
+
const versionControl = makeVersionControl();
|
|
507
469
|
const acquired: string[] = [];
|
|
508
|
-
(
|
|
470
|
+
(versionControl.acquireLock as ReturnType<typeof vi.fn>).mockImplementation(async (_w, _b, p: string) => {
|
|
509
471
|
acquired.push(p);
|
|
510
472
|
return { acquired: true, lock: { holderName: 'Alice' } };
|
|
511
473
|
});
|
|
@@ -515,7 +477,7 @@ describe('LockingFilesystem — the caller judges under the lock', () => {
|
|
|
515
477
|
{ path: 'knowledge-base/b.md', content: 'B' },
|
|
516
478
|
{ path: 'knowledge-base/c.md', content: 'C' },
|
|
517
479
|
];
|
|
518
|
-
await layer(
|
|
480
|
+
await layer(versionControl).writeFiles(writes, 'batch', [], async (pending) => {
|
|
519
481
|
// Every path of the batch is locked before the check gets to judge any.
|
|
520
482
|
expect([...acquired].sort()).toEqual(['knowledge-base/a.md', 'knowledge-base/b.md', 'knowledge-base/c.md']);
|
|
521
483
|
expect(pending.map((w) => w.path)).toEqual(writes.map((w) => w.path));
|
|
@@ -528,16 +490,16 @@ describe('LockingFilesystem — the caller judges under the lock', () => {
|
|
|
528
490
|
// The dropped path stays OUT of the commit's scope — an unscoped commit
|
|
529
491
|
// there would sweep in whatever another save left dirty on it — and
|
|
530
492
|
// releases untouched, because this batch never wrote a byte to it.
|
|
531
|
-
expect(
|
|
493
|
+
expect(versionControl.commitChanges).toHaveBeenCalledWith('ws-feat', USER, 'batch', [
|
|
532
494
|
'knowledge-base/a.md',
|
|
533
495
|
'knowledge-base/c.md',
|
|
534
496
|
]);
|
|
535
|
-
expect(
|
|
497
|
+
expect(versionControl.releaseLockUntouched).toHaveBeenCalledWith('ws-feat', 'feat', 'knowledge-base/b.md', USER);
|
|
536
498
|
});
|
|
537
499
|
|
|
538
500
|
it('a writeFiles check that drops EVERY write commits nothing and releases everything untouched', async () => {
|
|
539
|
-
const
|
|
540
|
-
const change = await layer(
|
|
501
|
+
const versionControl = makeVersionControl();
|
|
502
|
+
const change = await layer(versionControl).writeFiles(
|
|
541
503
|
[{ path: 'knowledge-base/a.md', content: 'A' }],
|
|
542
504
|
'batch',
|
|
543
505
|
[],
|
|
@@ -546,15 +508,15 @@ describe('LockingFilesystem — the caller judges under the lock', () => {
|
|
|
546
508
|
|
|
547
509
|
expect(change).toBeNull();
|
|
548
510
|
await expect(fs.access(path.join(root, 'knowledge-base/a.md'))).rejects.toThrow();
|
|
549
|
-
expect(
|
|
550
|
-
expect(
|
|
511
|
+
expect(versionControl.commitChanges).not.toHaveBeenCalled();
|
|
512
|
+
expect(versionControl.releaseLockUntouched).toHaveBeenCalledWith('ws-feat', 'feat', 'knowledge-base/a.md', USER);
|
|
551
513
|
});
|
|
552
514
|
|
|
553
515
|
it('a writeFiles check that THROWS refuses the whole batch, untouched', async () => {
|
|
554
|
-
const
|
|
516
|
+
const versionControl = makeVersionControl();
|
|
555
517
|
const refusal = new Error('nope');
|
|
556
518
|
await expect(
|
|
557
|
-
layer(
|
|
519
|
+
layer(versionControl).writeFiles(
|
|
558
520
|
[{ path: 'knowledge-base/a.md', content: 'A' }, { path: 'knowledge-base/b.md', content: 'B' }],
|
|
559
521
|
'batch',
|
|
560
522
|
[],
|
|
@@ -565,8 +527,8 @@ describe('LockingFilesystem — the caller judges under the lock', () => {
|
|
|
565
527
|
).rejects.toBe(refusal);
|
|
566
528
|
|
|
567
529
|
await expect(fs.access(path.join(root, 'knowledge-base/a.md'))).rejects.toThrow();
|
|
568
|
-
expect(
|
|
569
|
-
expect(
|
|
530
|
+
expect(versionControl.commitChanges).not.toHaveBeenCalled();
|
|
531
|
+
expect(versionControl.releaseLockUntouched).toHaveBeenCalledTimes(2);
|
|
570
532
|
});
|
|
571
533
|
});
|
|
572
534
|
|
|
@@ -589,11 +551,11 @@ describe('LockingFilesystem — creator read grants on creation', () => {
|
|
|
589
551
|
}
|
|
590
552
|
|
|
591
553
|
it('writeFile asks the planner about the new file and lands the content untouched', async () => {
|
|
592
|
-
const
|
|
554
|
+
const versionControl = makeVersionControl();
|
|
593
555
|
const creatorAccess = makeCreatorAccess();
|
|
594
556
|
const fsLayer = new LockingFilesystem(
|
|
595
557
|
{ basePath: root, contained: true },
|
|
596
|
-
{
|
|
558
|
+
{ versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB, creatorAccess },
|
|
597
559
|
);
|
|
598
560
|
await fsLayer.writeFile('knowledge-base/KnowledgeBase/new.md', '# New\n');
|
|
599
561
|
expect(creatorAccess.planForCreate).toHaveBeenCalledWith(
|
|
@@ -608,7 +570,7 @@ describe('LockingFilesystem — creator read grants on creation', () => {
|
|
|
608
570
|
});
|
|
609
571
|
|
|
610
572
|
it('writeFile seeds a subtree access.md (own lock cycle) before the file itself', async () => {
|
|
611
|
-
const
|
|
573
|
+
const versionControl = makeVersionControl();
|
|
612
574
|
const creatorAccess = makeCreatorAccess();
|
|
613
575
|
creatorAccess.planForCreate.mockImplementation(async (_w, _u, p: string) =>
|
|
614
576
|
p === 'knowledge-base/KnowledgeBase/Mine/doc.md'
|
|
@@ -622,10 +584,10 @@ describe('LockingFilesystem — creator read grants on creation', () => {
|
|
|
622
584
|
);
|
|
623
585
|
const fsLayer = new LockingFilesystem(
|
|
624
586
|
{ basePath: root, contained: true },
|
|
625
|
-
{
|
|
587
|
+
{ versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB, creatorAccess },
|
|
626
588
|
);
|
|
627
589
|
await fsLayer.writeFile('knowledge-base/KnowledgeBase/Mine/doc.md', 'body');
|
|
628
|
-
const locked = (
|
|
590
|
+
const locked = (versionControl.acquireLock as ReturnType<typeof vi.fn>).mock.calls.map(
|
|
629
591
|
(c) => c[2],
|
|
630
592
|
);
|
|
631
593
|
expect(locked).toEqual(['knowledge-base/KnowledgeBase/Mine/access.md', 'knowledge-base/KnowledgeBase/Mine/doc.md']);
|
|
@@ -638,7 +600,7 @@ describe('LockingFilesystem — creator read grants on creation', () => {
|
|
|
638
600
|
});
|
|
639
601
|
|
|
640
602
|
it('mkdir seeds the new folder access.md and then skips the .gitkeep (dir not empty)', async () => {
|
|
641
|
-
const
|
|
603
|
+
const versionControl = makeVersionControl();
|
|
642
604
|
const creatorAccess = makeCreatorAccess();
|
|
643
605
|
creatorAccess.planForCreate.mockImplementation(async (_w, _u, p: string, kind: string) =>
|
|
644
606
|
kind === 'dir'
|
|
@@ -651,7 +613,7 @@ describe('LockingFilesystem — creator read grants on creation', () => {
|
|
|
651
613
|
);
|
|
652
614
|
const fsLayer = new LockingFilesystem(
|
|
653
615
|
{ basePath: root, contained: true },
|
|
654
|
-
{
|
|
616
|
+
{ versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB, creatorAccess },
|
|
655
617
|
);
|
|
656
618
|
await fsLayer.mkdir('knowledge-base/KnowledgeBase/Projects');
|
|
657
619
|
expect(
|
|
@@ -663,7 +625,7 @@ describe('LockingFilesystem — creator read grants on creation', () => {
|
|
|
663
625
|
});
|
|
664
626
|
|
|
665
627
|
it('writeFiles folds seeds into the same atomic batch, deduped across files', async () => {
|
|
666
|
-
const
|
|
628
|
+
const versionControl = makeVersionControl();
|
|
667
629
|
const creatorAccess = makeCreatorAccess();
|
|
668
630
|
creatorAccess.planForCreate.mockResolvedValue({
|
|
669
631
|
kind: 'seed-access-md',
|
|
@@ -672,7 +634,7 @@ describe('LockingFilesystem — creator read grants on creation', () => {
|
|
|
672
634
|
});
|
|
673
635
|
const fsLayer = new LockingFilesystem(
|
|
674
636
|
{ basePath: root, contained: true },
|
|
675
|
-
{
|
|
637
|
+
{ versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB, creatorAccess },
|
|
676
638
|
);
|
|
677
639
|
await fsLayer.writeFiles(
|
|
678
640
|
[
|
|
@@ -685,8 +647,8 @@ describe('LockingFilesystem — creator read grants on creation', () => {
|
|
|
685
647
|
expect(
|
|
686
648
|
await fs.readFile(path.join(root, 'knowledge-base/KnowledgeBase/Mine/access.md'), 'utf-8'),
|
|
687
649
|
).toContain('Alice <alice@example.com>');
|
|
688
|
-
expect(
|
|
689
|
-
const locked = (
|
|
650
|
+
expect(versionControl.commitChanges).toHaveBeenCalledTimes(1);
|
|
651
|
+
const locked = (versionControl.acquireLock as ReturnType<typeof vi.fn>).mock.calls.map(
|
|
690
652
|
(c) => c[2],
|
|
691
653
|
);
|
|
692
654
|
expect(locked).toEqual([
|
|
@@ -707,34 +669,31 @@ describe('LockingFilesystem — creator read grants on creation', () => {
|
|
|
707
669
|
await fs.mkdir(path.join(root, 'knowledge-base/KnowledgeBase/Mine'), { recursive: true });
|
|
708
670
|
const seedContent = '---\nread:\n - Alice <alice@example.com>\n---\n';
|
|
709
671
|
await fs.writeFile(path.join(root, 'knowledge-base/KnowledgeBase/Mine/access.md'), seedContent);
|
|
710
|
-
const
|
|
672
|
+
const versionControl = makeVersionControl();
|
|
711
673
|
const creatorAccess = makeCreatorAccess();
|
|
712
674
|
creatorAccess.planForCreate.mockResolvedValue({
|
|
713
675
|
kind: 'seed-access-md',
|
|
714
676
|
wsRelPath: 'knowledge-base/KnowledgeBase/Mine/access.md',
|
|
715
677
|
apply: (current: string) => current, // grant already present → no-op
|
|
716
678
|
});
|
|
717
|
-
const emitted: { paths: string[] }[] = [];
|
|
718
679
|
const fsLayer = new LockingFilesystem(
|
|
719
680
|
{ basePath: root, contained: true },
|
|
720
681
|
{
|
|
721
|
-
|
|
682
|
+
versionControl,
|
|
722
683
|
workspaceId: 'ws-feat',
|
|
723
684
|
branch: 'feat',
|
|
724
685
|
user: USER, kbDirName: KB,
|
|
725
686
|
creatorAccess,
|
|
726
|
-
fileChanges: { emit: (c: { paths: string[] }) => emitted.push(c) } as never,
|
|
727
687
|
},
|
|
728
688
|
);
|
|
729
689
|
await fsLayer.writeFiles([{ path: 'knowledge-base/KnowledgeBase/Mine/doc.md', content: 'body' }], 'batch');
|
|
730
690
|
|
|
731
691
|
// The seed's lock cycle still ran (acquired + released)...
|
|
732
|
-
const locked = (
|
|
692
|
+
const locked = (versionControl.acquireLock as ReturnType<typeof vi.fn>).mock.calls.map((c) => c[2]);
|
|
733
693
|
expect(locked).toContain('knowledge-base/KnowledgeBase/Mine/access.md');
|
|
734
694
|
// ...but the commit is scoped to the caller's path only.
|
|
735
|
-
const commitPaths = (
|
|
695
|
+
const commitPaths = (versionControl.commitChanges as ReturnType<typeof vi.fn>).mock.calls[0][3];
|
|
736
696
|
expect(commitPaths).toEqual(['knowledge-base/KnowledgeBase/Mine/doc.md']);
|
|
737
|
-
expect(emitted[0].paths).toEqual(['knowledge-base/KnowledgeBase/Mine/doc.md']);
|
|
738
697
|
// The seeded file is untouched.
|
|
739
698
|
expect(await fs.readFile(path.join(root, 'knowledge-base/KnowledgeBase/Mine/access.md'), 'utf-8')).toBe(
|
|
740
699
|
seedContent,
|
|
@@ -756,7 +715,7 @@ describe('LockingFilesystem — creator read grants on creation', () => {
|
|
|
756
715
|
await fs.mkdir(path.join(root, 'knowledge-base/KnowledgeBase/Mine'), { recursive: true });
|
|
757
716
|
const priorSave = '---\nread:\n - Alice <alice@example.com>\n---\nprior queued bytes\n';
|
|
758
717
|
await fs.writeFile(path.join(root, 'knowledge-base/KnowledgeBase/Mine/access.md'), priorSave);
|
|
759
|
-
const
|
|
718
|
+
const versionControl = makeVersionControl();
|
|
760
719
|
const creatorAccess = makeCreatorAccess();
|
|
761
720
|
creatorAccess.planForCreate.mockResolvedValue({
|
|
762
721
|
kind: 'seed-access-md',
|
|
@@ -765,21 +724,21 @@ describe('LockingFilesystem — creator read grants on creation', () => {
|
|
|
765
724
|
});
|
|
766
725
|
const fsLayer = new LockingFilesystem(
|
|
767
726
|
{ basePath: root, contained: true },
|
|
768
|
-
{
|
|
727
|
+
{ versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB, creatorAccess },
|
|
769
728
|
);
|
|
770
729
|
await fsLayer.writeFiles([{ path: 'knowledge-base/KnowledgeBase/Mine/doc.md', content: 'body' }], 'batch');
|
|
771
730
|
|
|
772
731
|
// The batch's OWN committed path releases no-commit (clean — discard no-ops)...
|
|
773
|
-
expect(
|
|
732
|
+
expect(versionControl.releaseLockNoCommit).toHaveBeenCalledWith(
|
|
774
733
|
'ws-feat', 'feat', 'knowledge-base/KnowledgeBase/Mine/doc.md', USER,
|
|
775
734
|
);
|
|
776
735
|
// ...but the merely-locked seed path releases UNTOUCHED — never the
|
|
777
736
|
// enqueue, never the discard.
|
|
778
|
-
expect(
|
|
737
|
+
expect(versionControl.releaseLockUntouched).toHaveBeenCalledWith(
|
|
779
738
|
'ws-feat', 'feat', 'knowledge-base/KnowledgeBase/Mine/access.md', USER,
|
|
780
739
|
);
|
|
781
|
-
expect(
|
|
782
|
-
expect(
|
|
740
|
+
expect(versionControl.releaseLock).not.toHaveBeenCalled();
|
|
741
|
+
expect(versionControl.releaseLockNoCommit).not.toHaveBeenCalledWith(
|
|
783
742
|
'ws-feat', 'feat', 'knowledge-base/KnowledgeBase/Mine/access.md', USER,
|
|
784
743
|
);
|
|
785
744
|
});
|
|
@@ -788,13 +747,13 @@ describe('LockingFilesystem — creator read grants on creation', () => {
|
|
|
788
747
|
// First path acquires, second is contended: nothing has been written, so
|
|
789
748
|
// the first path may hold ONLY someone else's still-queued bytes — the
|
|
790
749
|
// unwind must neither discard them nor enqueue them under this user.
|
|
791
|
-
const
|
|
792
|
-
(
|
|
750
|
+
const versionControl = makeVersionControl();
|
|
751
|
+
(versionControl.acquireLock as ReturnType<typeof vi.fn>)
|
|
793
752
|
.mockResolvedValueOnce({ acquired: true, lock: { holderUserId: 'user-1', holderName: 'Alice' } })
|
|
794
753
|
.mockResolvedValue({ acquired: false, lock: { holderUserId: 'bob', holderName: 'Bob' } });
|
|
795
754
|
const fsLayer = new LockingFilesystem(
|
|
796
755
|
{ basePath: root, contained: true },
|
|
797
|
-
{
|
|
756
|
+
{ versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB },
|
|
798
757
|
);
|
|
799
758
|
await expect(
|
|
800
759
|
fsLayer.writeFiles(
|
|
@@ -805,9 +764,9 @@ describe('LockingFilesystem — creator read grants on creation', () => {
|
|
|
805
764
|
'batch',
|
|
806
765
|
),
|
|
807
766
|
).rejects.toThrow(/locked by Bob/);
|
|
808
|
-
expect(
|
|
809
|
-
expect(
|
|
810
|
-
expect(
|
|
767
|
+
expect(versionControl.releaseLockUntouched).toHaveBeenCalledWith('ws-feat', 'feat', 'knowledge-base/A.md', USER);
|
|
768
|
+
expect(versionControl.releaseLock).not.toHaveBeenCalled();
|
|
769
|
+
expect(versionControl.releaseLockNoCommit).not.toHaveBeenCalled();
|
|
811
770
|
}, 10_000);
|
|
812
771
|
|
|
813
772
|
it('a DELETE batch whose later lock is contended deletes nothing — a folder never half-disappears', async () => {
|
|
@@ -817,13 +776,13 @@ describe('LockingFilesystem — creator read grants on creation', () => {
|
|
|
817
776
|
await fs.mkdir(path.join(root, 'knowledge-base/Archive'), { recursive: true });
|
|
818
777
|
await fs.writeFile(path.join(root, 'knowledge-base/Archive/a.md'), 'a');
|
|
819
778
|
await fs.writeFile(path.join(root, 'knowledge-base/Archive/b.md'), 'b');
|
|
820
|
-
const
|
|
821
|
-
(
|
|
779
|
+
const versionControl = makeVersionControl();
|
|
780
|
+
(versionControl.acquireLock as ReturnType<typeof vi.fn>)
|
|
822
781
|
.mockResolvedValueOnce({ acquired: true, lock: { holderUserId: 'user-1', holderName: 'Alice' } })
|
|
823
782
|
.mockResolvedValue({ acquired: false, lock: { holderUserId: 'bob', holderName: 'Bob' } });
|
|
824
783
|
const fsLayer = new LockingFilesystem(
|
|
825
784
|
{ basePath: root, contained: true },
|
|
826
|
-
{
|
|
785
|
+
{ versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB },
|
|
827
786
|
);
|
|
828
787
|
|
|
829
788
|
await expect(
|
|
@@ -832,7 +791,7 @@ describe('LockingFilesystem — creator read grants on creation', () => {
|
|
|
832
791
|
|
|
833
792
|
expect(await fs.readFile(path.join(root, 'knowledge-base/Archive/a.md'), 'utf-8')).toBe('a');
|
|
834
793
|
expect(await fs.readFile(path.join(root, 'knowledge-base/Archive/b.md'), 'utf-8')).toBe('b');
|
|
835
|
-
expect(
|
|
794
|
+
expect(versionControl.commitChanges).not.toHaveBeenCalled();
|
|
836
795
|
}, 10_000);
|
|
837
796
|
|
|
838
797
|
it('a seed write that dies MID-WRITE restores the pre-image and releases UNTOUCHED', async () => {
|
|
@@ -845,7 +804,7 @@ describe('LockingFilesystem — creator read grants on creation', () => {
|
|
|
845
804
|
const priorBytes = 'prior queued bytes\n';
|
|
846
805
|
const seedPath = 'knowledge-base/KnowledgeBase/Mine/access.md';
|
|
847
806
|
await fs.writeFile(path.join(root, seedPath), priorBytes);
|
|
848
|
-
const
|
|
807
|
+
const versionControl = makeVersionControl();
|
|
849
808
|
const creatorAccess = makeCreatorAccess();
|
|
850
809
|
creatorAccess.planForCreate.mockResolvedValue({
|
|
851
810
|
kind: 'seed-access-md',
|
|
@@ -854,7 +813,7 @@ describe('LockingFilesystem — creator read grants on creation', () => {
|
|
|
854
813
|
});
|
|
855
814
|
const fsLayer = new LockingFilesystem(
|
|
856
815
|
{ basePath: root, contained: true },
|
|
857
|
-
{
|
|
816
|
+
{ versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB, creatorAccess },
|
|
858
817
|
);
|
|
859
818
|
// Fail the SEED write once (simulating a mid-write death that left
|
|
860
819
|
// partial bytes); every other write — including the restore — passes
|
|
@@ -879,13 +838,13 @@ describe('LockingFilesystem — creator read grants on creation', () => {
|
|
|
879
838
|
// Pre-image restored — the partial bytes did not outlive the batch.
|
|
880
839
|
expect(await fs.readFile(path.join(root, seedPath), 'utf-8')).toBe(priorBytes);
|
|
881
840
|
// The failed seed stays OUT of the commit scope and releases untouched.
|
|
882
|
-
const commitPaths = (
|
|
841
|
+
const commitPaths = (versionControl.commitChanges as ReturnType<typeof vi.fn>).mock.calls[0][3];
|
|
883
842
|
expect(commitPaths).toEqual(['knowledge-base/KnowledgeBase/Mine/doc.md']);
|
|
884
|
-
expect(
|
|
885
|
-
expect(
|
|
843
|
+
expect(versionControl.releaseLockUntouched).toHaveBeenCalledWith('ws-feat', 'feat', seedPath, USER);
|
|
844
|
+
expect(versionControl.releaseLockNoCommit).not.toHaveBeenCalledWith(
|
|
886
845
|
'ws-feat', 'feat', seedPath, USER,
|
|
887
846
|
);
|
|
888
|
-
expect(
|
|
847
|
+
expect(versionControl.releaseLock).not.toHaveBeenCalled();
|
|
889
848
|
});
|
|
890
849
|
|
|
891
850
|
it('a LANDED seed rolls back to its pre-image when the batch commit fails — grant bytes must not outlive the batch uncommitted', async () => {
|
|
@@ -899,8 +858,8 @@ describe('LockingFilesystem — creator read grants on creation', () => {
|
|
|
899
858
|
const priorBytes = 'prior queued bytes\n';
|
|
900
859
|
const seedPath = 'knowledge-base/KnowledgeBase/Mine/access.md';
|
|
901
860
|
await fs.writeFile(path.join(root, seedPath), priorBytes);
|
|
902
|
-
const
|
|
903
|
-
(
|
|
861
|
+
const versionControl = makeVersionControl();
|
|
862
|
+
(versionControl.commitChanges as ReturnType<typeof vi.fn>).mockRejectedValue(
|
|
904
863
|
new Error('commit exploded'),
|
|
905
864
|
);
|
|
906
865
|
const creatorAccess = makeCreatorAccess();
|
|
@@ -911,7 +870,7 @@ describe('LockingFilesystem — creator read grants on creation', () => {
|
|
|
911
870
|
});
|
|
912
871
|
const fsLayer = new LockingFilesystem(
|
|
913
872
|
{ basePath: root, contained: true },
|
|
914
|
-
{
|
|
873
|
+
{ versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB, creatorAccess },
|
|
915
874
|
);
|
|
916
875
|
await expect(
|
|
917
876
|
fsLayer.writeFiles([{ path: 'knowledge-base/KnowledgeBase/Mine/doc.md', content: 'body' }], 'batch'),
|
|
@@ -920,11 +879,11 @@ describe('LockingFilesystem — creator read grants on creation', () => {
|
|
|
920
879
|
expect(await fs.readFile(path.join(root, seedPath), 'utf-8')).toBe(priorBytes);
|
|
921
880
|
// …and the seed releases UNTOUCHED (the prior queued bytes are not this
|
|
922
881
|
// batch's to discard), while the caller's own write releases via discard.
|
|
923
|
-
expect(
|
|
924
|
-
expect(
|
|
882
|
+
expect(versionControl.releaseLockUntouched).toHaveBeenCalledWith('ws-feat', 'feat', seedPath, USER);
|
|
883
|
+
expect(versionControl.releaseLockNoCommit).toHaveBeenCalledWith(
|
|
925
884
|
'ws-feat', 'feat', 'knowledge-base/KnowledgeBase/Mine/doc.md', USER,
|
|
926
885
|
);
|
|
927
|
-
expect(
|
|
886
|
+
expect(versionControl.releaseLockNoCommit).not.toHaveBeenCalledWith(
|
|
928
887
|
'ws-feat', 'feat', seedPath, USER,
|
|
929
888
|
);
|
|
930
889
|
});
|
|
@@ -937,7 +896,7 @@ describe('LockingFilesystem — creator read grants on creation', () => {
|
|
|
937
896
|
await fs.mkdir(path.join(root, 'knowledge-base/KnowledgeBase/Mine'), { recursive: true });
|
|
938
897
|
const seedPath = 'knowledge-base/KnowledgeBase/Mine/access.md';
|
|
939
898
|
await fs.writeFile(path.join(root, seedPath), 'prior\n');
|
|
940
|
-
const
|
|
899
|
+
const versionControl = makeVersionControl();
|
|
941
900
|
const creatorAccess = makeCreatorAccess();
|
|
942
901
|
creatorAccess.planForCreate.mockResolvedValue({
|
|
943
902
|
kind: 'seed-access-md',
|
|
@@ -946,7 +905,7 @@ describe('LockingFilesystem — creator read grants on creation', () => {
|
|
|
946
905
|
});
|
|
947
906
|
const fsLayer = new LockingFilesystem(
|
|
948
907
|
{ basePath: root, contained: true },
|
|
949
|
-
{
|
|
908
|
+
{ versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB, creatorAccess },
|
|
950
909
|
);
|
|
951
910
|
const original = LocalFilesystem.prototype.writeFile;
|
|
952
911
|
const spy = vi
|
|
@@ -962,12 +921,12 @@ describe('LockingFilesystem — creator read grants on creation', () => {
|
|
|
962
921
|
}
|
|
963
922
|
// The unrecoverable seed path releases via the discard, everything else
|
|
964
923
|
// untouched-or-no-commit as usual; the batch itself still succeeds.
|
|
965
|
-
expect(
|
|
966
|
-
expect(
|
|
924
|
+
expect(versionControl.releaseLockNoCommit).toHaveBeenCalledWith('ws-feat', 'feat', seedPath, USER);
|
|
925
|
+
expect(versionControl.releaseLockUntouched).not.toHaveBeenCalledWith(
|
|
967
926
|
'ws-feat', 'feat', seedPath, USER,
|
|
968
927
|
);
|
|
969
|
-
expect(
|
|
970
|
-
const commitPaths = (
|
|
928
|
+
expect(versionControl.releaseLock).not.toHaveBeenCalled();
|
|
929
|
+
const commitPaths = (versionControl.commitChanges as ReturnType<typeof vi.fn>).mock.calls[0][3];
|
|
971
930
|
expect(commitPaths).toEqual(['knowledge-base/KnowledgeBase/Mine/doc.md']);
|
|
972
931
|
});
|
|
973
932
|
|
|
@@ -980,8 +939,8 @@ describe('LockingFilesystem — creator read grants on creation', () => {
|
|
|
980
939
|
await fs.mkdir(path.join(root, 'knowledge-base/KnowledgeBase/Mine'), { recursive: true });
|
|
981
940
|
const seedPath = 'knowledge-base/KnowledgeBase/Mine/access.md';
|
|
982
941
|
await fs.writeFile(path.join(root, seedPath), 'grant already present\n');
|
|
983
|
-
const
|
|
984
|
-
(
|
|
942
|
+
const versionControl = makeVersionControl();
|
|
943
|
+
(versionControl.commitChanges as ReturnType<typeof vi.fn>).mockRejectedValue(
|
|
985
944
|
new PushNeedsAgentResolutionError('feat', '(batch)', 'non-fast-forward', 'rebase failed'),
|
|
986
945
|
);
|
|
987
946
|
const creatorAccess = makeCreatorAccess();
|
|
@@ -992,21 +951,21 @@ describe('LockingFilesystem — creator read grants on creation', () => {
|
|
|
992
951
|
});
|
|
993
952
|
const fsLayer = new LockingFilesystem(
|
|
994
953
|
{ basePath: root, contained: true },
|
|
995
|
-
{
|
|
954
|
+
{ versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB, creatorAccess },
|
|
996
955
|
);
|
|
997
956
|
await expect(
|
|
998
957
|
fsLayer.writeFiles([{ path: 'knowledge-base/KnowledgeBase/Mine/doc.md', content: 'body' }], 'batch'),
|
|
999
958
|
).rejects.toBeInstanceOf(PushNeedsAgentResolutionError);
|
|
1000
|
-
expect(
|
|
959
|
+
expect(versionControl.releaseLock).toHaveBeenCalledWith(
|
|
1001
960
|
'ws-feat', 'feat', 'knowledge-base/KnowledgeBase/Mine/doc.md', USER,
|
|
1002
961
|
);
|
|
1003
|
-
expect(
|
|
1004
|
-
expect(
|
|
1005
|
-
expect(
|
|
962
|
+
expect(versionControl.releaseLockUntouched).toHaveBeenCalledWith('ws-feat', 'feat', seedPath, USER);
|
|
963
|
+
expect(versionControl.releaseLock).not.toHaveBeenCalledWith('ws-feat', 'feat', seedPath, USER);
|
|
964
|
+
expect(versionControl.releaseLockNoCommit).not.toHaveBeenCalled();
|
|
1006
965
|
});
|
|
1007
966
|
|
|
1008
967
|
it('a LANDED seed rides the commit scope with the caller paths', async () => {
|
|
1009
|
-
const
|
|
968
|
+
const versionControl = makeVersionControl();
|
|
1010
969
|
const creatorAccess = makeCreatorAccess();
|
|
1011
970
|
creatorAccess.planForCreate.mockResolvedValue({
|
|
1012
971
|
kind: 'seed-access-md',
|
|
@@ -1015,20 +974,20 @@ describe('LockingFilesystem — creator read grants on creation', () => {
|
|
|
1015
974
|
});
|
|
1016
975
|
const fsLayer = new LockingFilesystem(
|
|
1017
976
|
{ basePath: root, contained: true },
|
|
1018
|
-
{
|
|
977
|
+
{ versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB, creatorAccess },
|
|
1019
978
|
);
|
|
1020
979
|
await fsLayer.writeFiles([{ path: 'knowledge-base/KnowledgeBase/Mine/doc.md', content: 'body' }], 'batch');
|
|
1021
|
-
const commitPaths = (
|
|
980
|
+
const commitPaths = (versionControl.commitChanges as ReturnType<typeof vi.fn>).mock.calls[0][3];
|
|
1022
981
|
expect(commitPaths).toEqual(['knowledge-base/KnowledgeBase/Mine/doc.md', 'knowledge-base/KnowledgeBase/Mine/access.md']);
|
|
1023
982
|
});
|
|
1024
983
|
|
|
1025
984
|
it('a planner failure never blocks the write', async () => {
|
|
1026
|
-
const
|
|
985
|
+
const versionControl = makeVersionControl();
|
|
1027
986
|
const creatorAccess = makeCreatorAccess();
|
|
1028
987
|
creatorAccess.planForCreate.mockRejectedValue(new Error('planner down'));
|
|
1029
988
|
const fsLayer = new LockingFilesystem(
|
|
1030
989
|
{ basePath: root, contained: true },
|
|
1031
|
-
{
|
|
990
|
+
{ versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB, creatorAccess },
|
|
1032
991
|
);
|
|
1033
992
|
await fsLayer.writeFile('knowledge-base/KnowledgeBase/new.md', 'x');
|
|
1034
993
|
expect(await fs.readFile(path.join(root, 'knowledge-base/KnowledgeBase/new.md'), 'utf-8')).toBe('x');
|
|
@@ -1064,50 +1023,50 @@ describe('LockingFilesystem refuses to create anything outside the repository fo
|
|
|
1064
1023
|
};
|
|
1065
1024
|
}
|
|
1066
1025
|
|
|
1067
|
-
function layer(
|
|
1026
|
+
function layer(versionControl: IVersionControlService) {
|
|
1068
1027
|
const creatorAccess = makeCreatorAccess();
|
|
1069
1028
|
const validateWrite = vi.fn();
|
|
1070
1029
|
const fsLayer = new LockingFilesystem(
|
|
1071
1030
|
{ basePath: root, contained: true },
|
|
1072
|
-
{
|
|
1031
|
+
{ versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB, creatorAccess, validateWrite },
|
|
1073
1032
|
);
|
|
1074
1033
|
return { fsLayer, creatorAccess, validateWrite };
|
|
1075
1034
|
}
|
|
1076
1035
|
|
|
1077
1036
|
async function expectRefused(
|
|
1078
1037
|
op: Promise<unknown>,
|
|
1079
|
-
|
|
1038
|
+
versionControl: IVersionControlService,
|
|
1080
1039
|
creatorAccess: ReturnType<typeof makeCreatorAccess>,
|
|
1081
1040
|
validateWrite: ReturnType<typeof vi.fn>,
|
|
1082
1041
|
): Promise<void> {
|
|
1083
|
-
await expect(op).rejects.toBeInstanceOf(
|
|
1042
|
+
await expect(op).rejects.toBeInstanceOf(VersionControlValidationError);
|
|
1084
1043
|
// The corrected path is spelled out, under the clone folder.
|
|
1085
1044
|
await expect(op).rejects.toThrow('"knowledge-base/KnowledgeBase/Reviews');
|
|
1086
1045
|
// Refused before any side effect.
|
|
1087
|
-
expect(
|
|
1088
|
-
expect(
|
|
1089
|
-
expect(
|
|
1090
|
-
expect(
|
|
1046
|
+
expect(versionControl.acquireLock).not.toHaveBeenCalled();
|
|
1047
|
+
expect(versionControl.releaseLock).not.toHaveBeenCalled();
|
|
1048
|
+
expect(versionControl.releaseLockNoCommit).not.toHaveBeenCalled();
|
|
1049
|
+
expect(versionControl.commitChanges).not.toHaveBeenCalled();
|
|
1091
1050
|
expect(creatorAccess.planForCreate).not.toHaveBeenCalled();
|
|
1092
1051
|
expect(validateWrite).not.toHaveBeenCalled();
|
|
1093
1052
|
await expect(fs.access(path.join(root, 'KnowledgeBase'))).rejects.toBeDefined();
|
|
1094
1053
|
}
|
|
1095
1054
|
|
|
1096
1055
|
it('writeFile', async () => {
|
|
1097
|
-
const
|
|
1098
|
-
const { fsLayer, creatorAccess, validateWrite } = layer(
|
|
1099
|
-
await expectRefused(fsLayer.writeFile(STRAY, '<p>review</p>'),
|
|
1056
|
+
const versionControl = makeVersionControl();
|
|
1057
|
+
const { fsLayer, creatorAccess, validateWrite } = layer(versionControl);
|
|
1058
|
+
await expectRefused(fsLayer.writeFile(STRAY, '<p>review</p>'), versionControl, creatorAccess, validateWrite);
|
|
1100
1059
|
});
|
|
1101
1060
|
|
|
1102
1061
|
it('appendFile', async () => {
|
|
1103
|
-
const
|
|
1104
|
-
const { fsLayer, creatorAccess, validateWrite } = layer(
|
|
1105
|
-
await expectRefused(fsLayer.appendFile('KnowledgeBase/Reviews/review.log', 'line\n'),
|
|
1062
|
+
const versionControl = makeVersionControl();
|
|
1063
|
+
const { fsLayer, creatorAccess, validateWrite } = layer(versionControl);
|
|
1064
|
+
await expectRefused(fsLayer.appendFile('KnowledgeBase/Reviews/review.log', 'line\n'), versionControl, creatorAccess, validateWrite);
|
|
1106
1065
|
});
|
|
1107
1066
|
|
|
1108
1067
|
it('writeFiles refuses the whole batch when one path is outside: nothing written, nothing locked', async () => {
|
|
1109
|
-
const
|
|
1110
|
-
const { fsLayer, creatorAccess, validateWrite } = layer(
|
|
1068
|
+
const versionControl = makeVersionControl();
|
|
1069
|
+
const { fsLayer, creatorAccess, validateWrite } = layer(versionControl);
|
|
1111
1070
|
await expectRefused(
|
|
1112
1071
|
fsLayer.writeFiles(
|
|
1113
1072
|
[
|
|
@@ -1116,7 +1075,7 @@ describe('LockingFilesystem refuses to create anything outside the repository fo
|
|
|
1116
1075
|
],
|
|
1117
1076
|
'batch',
|
|
1118
1077
|
),
|
|
1119
|
-
|
|
1078
|
+
versionControl,
|
|
1120
1079
|
creatorAccess,
|
|
1121
1080
|
validateWrite,
|
|
1122
1081
|
);
|
|
@@ -1124,19 +1083,19 @@ describe('LockingFilesystem refuses to create anything outside the repository fo
|
|
|
1124
1083
|
});
|
|
1125
1084
|
|
|
1126
1085
|
it('mkdir', async () => {
|
|
1127
|
-
const
|
|
1128
|
-
const { fsLayer, creatorAccess, validateWrite } = layer(
|
|
1129
|
-
await expectRefused(fsLayer.mkdir('KnowledgeBase/Reviews'),
|
|
1086
|
+
const versionControl = makeVersionControl();
|
|
1087
|
+
const { fsLayer, creatorAccess, validateWrite } = layer(versionControl);
|
|
1088
|
+
await expectRefused(fsLayer.mkdir('KnowledgeBase/Reviews'), versionControl, creatorAccess, validateWrite);
|
|
1130
1089
|
});
|
|
1131
1090
|
|
|
1132
1091
|
it('moveFile refuses a destination outside the repository and leaves the source in place', async () => {
|
|
1133
1092
|
await fs.mkdir(path.join(root, 'knowledge-base/KnowledgeBase'), { recursive: true });
|
|
1134
1093
|
await fs.writeFile(path.join(root, 'knowledge-base/KnowledgeBase/PR-12.html'), 'x');
|
|
1135
|
-
const
|
|
1136
|
-
const { fsLayer, creatorAccess, validateWrite } = layer(
|
|
1094
|
+
const versionControl = makeVersionControl();
|
|
1095
|
+
const { fsLayer, creatorAccess, validateWrite } = layer(versionControl);
|
|
1137
1096
|
await expectRefused(
|
|
1138
1097
|
fsLayer.moveFile('knowledge-base/KnowledgeBase/PR-12.html', STRAY),
|
|
1139
|
-
|
|
1098
|
+
versionControl,
|
|
1140
1099
|
creatorAccess,
|
|
1141
1100
|
validateWrite,
|
|
1142
1101
|
);
|
|
@@ -1146,11 +1105,11 @@ describe('LockingFilesystem refuses to create anything outside the repository fo
|
|
|
1146
1105
|
it('copyFile refuses a destination outside the repository', async () => {
|
|
1147
1106
|
await fs.mkdir(path.join(root, 'knowledge-base/KnowledgeBase'), { recursive: true });
|
|
1148
1107
|
await fs.writeFile(path.join(root, 'knowledge-base/KnowledgeBase/PR-12.html'), 'x');
|
|
1149
|
-
const
|
|
1150
|
-
const { fsLayer, creatorAccess, validateWrite } = layer(
|
|
1108
|
+
const versionControl = makeVersionControl();
|
|
1109
|
+
const { fsLayer, creatorAccess, validateWrite } = layer(versionControl);
|
|
1151
1110
|
await expectRefused(
|
|
1152
1111
|
fsLayer.copyFile('knowledge-base/KnowledgeBase/PR-12.html', STRAY),
|
|
1153
|
-
|
|
1112
|
+
versionControl,
|
|
1154
1113
|
creatorAccess,
|
|
1155
1114
|
validateWrite,
|
|
1156
1115
|
);
|
|
@@ -1160,20 +1119,20 @@ describe('LockingFilesystem refuses to create anything outside the repository fo
|
|
|
1160
1119
|
// Containment is checked against the WORKSPACE dir, so the underlying
|
|
1161
1120
|
// filesystem would happily resolve `knowledge-base/../stray.md` to a file
|
|
1162
1121
|
// next to the clone. The prefix alone is not proof of being inside.
|
|
1163
|
-
const
|
|
1164
|
-
const { fsLayer, creatorAccess, validateWrite } = layer(
|
|
1165
|
-
await expect(fsLayer.writeFile('knowledge-base/../stray.md', 'x')).rejects.toBeInstanceOf(
|
|
1166
|
-
expect(
|
|
1122
|
+
const versionControl = makeVersionControl();
|
|
1123
|
+
const { fsLayer, creatorAccess, validateWrite } = layer(versionControl);
|
|
1124
|
+
await expect(fsLayer.writeFile('knowledge-base/../stray.md', 'x')).rejects.toBeInstanceOf(VersionControlValidationError);
|
|
1125
|
+
expect(versionControl.acquireLock).not.toHaveBeenCalled();
|
|
1167
1126
|
expect(creatorAccess.planForCreate).not.toHaveBeenCalled();
|
|
1168
1127
|
expect(validateWrite).not.toHaveBeenCalled();
|
|
1169
1128
|
await expect(fs.access(path.join(root, 'stray.md'))).rejects.toBeDefined();
|
|
1170
1129
|
});
|
|
1171
1130
|
|
|
1172
1131
|
it('the folder is matched as a whole segment: `knowledge-based/x.md` is outside too', async () => {
|
|
1173
|
-
const
|
|
1174
|
-
const { fsLayer } = layer(
|
|
1175
|
-
await expect(fsLayer.writeFile('knowledge-based/x.md', 'x')).rejects.toBeInstanceOf(
|
|
1176
|
-
expect(
|
|
1132
|
+
const versionControl = makeVersionControl();
|
|
1133
|
+
const { fsLayer } = layer(versionControl);
|
|
1134
|
+
await expect(fsLayer.writeFile('knowledge-based/x.md', 'x')).rejects.toBeInstanceOf(VersionControlValidationError);
|
|
1135
|
+
expect(versionControl.acquireLock).not.toHaveBeenCalled();
|
|
1177
1136
|
});
|
|
1178
1137
|
|
|
1179
1138
|
it('deleteFile still removes a file the old behaviour left beside the repository', async () => {
|
|
@@ -1181,8 +1140,8 @@ describe('LockingFilesystem refuses to create anything outside the repository fo
|
|
|
1181
1140
|
// created before this guard existed must be able to clean it up.
|
|
1182
1141
|
await fs.mkdir(path.join(root, 'KnowledgeBase/Reviews'), { recursive: true });
|
|
1183
1142
|
await fs.writeFile(path.join(root, STRAY), 'stray');
|
|
1184
|
-
const
|
|
1185
|
-
const { fsLayer } = layer(
|
|
1143
|
+
const versionControl = makeVersionControl();
|
|
1144
|
+
const { fsLayer } = layer(versionControl);
|
|
1186
1145
|
await fsLayer.deleteFile(STRAY);
|
|
1187
1146
|
await expect(fs.access(path.join(root, STRAY))).rejects.toBeDefined();
|
|
1188
1147
|
});
|
|
@@ -1191,8 +1150,8 @@ describe('LockingFilesystem refuses to create anything outside the repository fo
|
|
|
1191
1150
|
await fs.mkdir(path.join(root, 'KnowledgeBase/Reviews'), { recursive: true });
|
|
1192
1151
|
await fs.writeFile(path.join(root, STRAY), 'stray');
|
|
1193
1152
|
await fs.mkdir(path.join(root, 'knowledge-base'), { recursive: true });
|
|
1194
|
-
const
|
|
1195
|
-
const { fsLayer } = layer(
|
|
1153
|
+
const versionControl = makeVersionControl();
|
|
1154
|
+
const { fsLayer } = layer(versionControl);
|
|
1196
1155
|
await fsLayer.moveFile(STRAY, CORRECTED);
|
|
1197
1156
|
expect(await fs.readFile(path.join(root, CORRECTED), 'utf-8')).toBe('stray');
|
|
1198
1157
|
await expect(fs.access(path.join(root, STRAY))).rejects.toBeDefined();
|
|
@@ -1217,85 +1176,85 @@ describe('LockingFilesystem — the agent roles.yaml gate covers every op that l
|
|
|
1217
1176
|
await fs.rm(root, { recursive: true, force: true });
|
|
1218
1177
|
});
|
|
1219
1178
|
|
|
1220
|
-
function layer(
|
|
1179
|
+
function layer(versionControl: IVersionControlService) {
|
|
1221
1180
|
const validateWrite = makeAgentRolesYamlWriteValidator(KB, () =>
|
|
1222
1181
|
fs.readFile(path.join(root, ROLES), 'utf-8').catch(() => null),
|
|
1223
1182
|
);
|
|
1224
1183
|
return new LockingFilesystem(
|
|
1225
1184
|
{ basePath: root, contained: true },
|
|
1226
|
-
{
|
|
1185
|
+
{ versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB, validateWrite },
|
|
1227
1186
|
);
|
|
1228
1187
|
}
|
|
1229
1188
|
|
|
1230
|
-
async function expectRefused(op: Promise<unknown>,
|
|
1231
|
-
await expect(op).rejects.
|
|
1232
|
-
expect(
|
|
1189
|
+
async function expectRefused(op: Promise<unknown>, versionControl: IVersionControlService): Promise<void> {
|
|
1190
|
+
await expect(op).rejects.toMatchObject({ status: 422, payload: { kind: 'roles-yaml-new-role' } });
|
|
1191
|
+
expect(versionControl.acquireLock).not.toHaveBeenCalled();
|
|
1233
1192
|
expect(await fs.readFile(path.join(root, ROLES), 'utf-8')).toBe(CURRENT);
|
|
1234
1193
|
}
|
|
1235
1194
|
|
|
1236
1195
|
it('writeFile', async () => {
|
|
1237
|
-
const
|
|
1238
|
-
await expectRefused(layer(
|
|
1196
|
+
const versionControl = makeVersionControl();
|
|
1197
|
+
await expectRefused(layer(versionControl).writeFile(ROLES, CURRENT + NEW_ROLE), versionControl);
|
|
1239
1198
|
});
|
|
1240
1199
|
|
|
1241
1200
|
it('writeFiles refuses the whole batch', async () => {
|
|
1242
|
-
const
|
|
1201
|
+
const versionControl = makeVersionControl();
|
|
1243
1202
|
await expectRefused(
|
|
1244
|
-
layer(
|
|
1245
|
-
|
|
1203
|
+
layer(versionControl).writeFiles([{ path: `${KB}/KnowledgeBase/ok.md`, content: 'fine' }, { path: ROLES, content: CURRENT + NEW_ROLE }], 'batch'),
|
|
1204
|
+
versionControl,
|
|
1246
1205
|
);
|
|
1247
1206
|
await expect(fs.access(path.join(root, KB, 'KnowledgeBase/ok.md'))).rejects.toBeDefined();
|
|
1248
1207
|
});
|
|
1249
1208
|
|
|
1250
1209
|
it('appendFile, judged by the file it would leave', async () => {
|
|
1251
|
-
const
|
|
1252
|
-
await expectRefused(layer(
|
|
1210
|
+
const versionControl = makeVersionControl();
|
|
1211
|
+
await expectRefused(layer(versionControl).appendFile(ROLES, NEW_ROLE), versionControl);
|
|
1253
1212
|
});
|
|
1254
1213
|
|
|
1255
1214
|
it('copyFile, judged by the source bytes', async () => {
|
|
1256
|
-
const
|
|
1257
|
-
await expectRefused(layer(
|
|
1215
|
+
const versionControl = makeVersionControl();
|
|
1216
|
+
await expectRefused(layer(versionControl).copyFile(DRAFT, ROLES, { overwrite: true }), versionControl);
|
|
1258
1217
|
});
|
|
1259
1218
|
|
|
1260
1219
|
it('moveFile, leaving the source where it was', async () => {
|
|
1261
|
-
const
|
|
1262
|
-
await expectRefused(layer(
|
|
1220
|
+
const versionControl = makeVersionControl();
|
|
1221
|
+
await expectRefused(layer(versionControl).moveFile(DRAFT, ROLES, { overwrite: true }), versionControl);
|
|
1263
1222
|
expect(await fs.readFile(path.join(root, DRAFT), 'utf-8')).toBe(CURRENT + NEW_ROLE);
|
|
1264
1223
|
});
|
|
1265
1224
|
|
|
1266
1225
|
it('a members-only write and append land as before', async () => {
|
|
1267
|
-
const
|
|
1268
|
-
const fsLayer = layer(
|
|
1226
|
+
const versionControl = makeVersionControl();
|
|
1227
|
+
const fsLayer = layer(versionControl);
|
|
1269
1228
|
await fsLayer.writeFile(ROLES, `${CURRENT} - b@x.eu\n`);
|
|
1270
1229
|
await fsLayer.appendFile(ROLES, ' - c@x.eu\n');
|
|
1271
1230
|
expect(await fs.readFile(path.join(root, ROLES), 'utf-8')).toBe(`${CURRENT} - b@x.eu\n - c@x.eu\n`);
|
|
1272
|
-
expect(
|
|
1231
|
+
expect(versionControl.releaseLock).toHaveBeenCalledTimes(2);
|
|
1273
1232
|
});
|
|
1274
1233
|
|
|
1275
1234
|
describe('judged again under the lock, against what a concurrent writer left there', () => {
|
|
1276
1235
|
const WITH_PHOENIX = CURRENT + NEW_ROLE;
|
|
1277
1236
|
|
|
1278
|
-
/** A
|
|
1279
|
-
function
|
|
1280
|
-
const
|
|
1281
|
-
const acquired = (
|
|
1237
|
+
/** A version-control service whose acquire lets `race` rewrite disk first — the lock holder that finished just before us. */
|
|
1238
|
+
function racingVersionControl(race: () => Promise<void>): IVersionControlService {
|
|
1239
|
+
const versionControl = makeVersionControl();
|
|
1240
|
+
const acquired = (versionControl.acquireLock as ReturnType<typeof vi.fn>).getMockImplementation()!;
|
|
1282
1241
|
let raced = false;
|
|
1283
|
-
(
|
|
1242
|
+
(versionControl.acquireLock as ReturnType<typeof vi.fn>).mockImplementation(async (...args: unknown[]) => {
|
|
1284
1243
|
if (!raced) {
|
|
1285
1244
|
raced = true;
|
|
1286
1245
|
await race();
|
|
1287
1246
|
}
|
|
1288
1247
|
return acquired(...args);
|
|
1289
1248
|
});
|
|
1290
|
-
return
|
|
1249
|
+
return versionControl;
|
|
1291
1250
|
}
|
|
1292
1251
|
|
|
1293
|
-
async function expectRefusedUnderLock(op: Promise<unknown>,
|
|
1294
|
-
await expect(op).rejects.
|
|
1295
|
-
expect(
|
|
1296
|
-
expect(
|
|
1297
|
-
expect(
|
|
1298
|
-
expect(
|
|
1252
|
+
async function expectRefusedUnderLock(op: Promise<unknown>, versionControl: IVersionControlService, locks: number): Promise<void> {
|
|
1253
|
+
await expect(op).rejects.toMatchObject({ status: 422, payload: { kind: 'roles-yaml-new-role' } });
|
|
1254
|
+
expect(versionControl.releaseLockUntouched).toHaveBeenCalledTimes(locks);
|
|
1255
|
+
expect(versionControl.releaseLockNoCommit).not.toHaveBeenCalled();
|
|
1256
|
+
expect(versionControl.releaseLock).not.toHaveBeenCalled();
|
|
1257
|
+
expect(versionControl.commitChanges).not.toHaveBeenCalled();
|
|
1299
1258
|
expect(await fs.readFile(path.join(root, ROLES), 'utf-8')).toBe(CURRENT);
|
|
1300
1259
|
}
|
|
1301
1260
|
|
|
@@ -1306,15 +1265,15 @@ describe('LockingFilesystem — the agent roles.yaml gate covers every op that l
|
|
|
1306
1265
|
});
|
|
1307
1266
|
|
|
1308
1267
|
it('writeFile cannot reinstate a role deleted while it waited for the lock', async () => {
|
|
1309
|
-
const
|
|
1310
|
-
await expectRefusedUnderLock(layer(
|
|
1268
|
+
const versionControl = racingVersionControl(deletePhoenix);
|
|
1269
|
+
await expectRefusedUnderLock(layer(versionControl).writeFile(ROLES, `${WITH_PHOENIX} - q@x.eu\n`), versionControl, 1);
|
|
1311
1270
|
});
|
|
1312
1271
|
|
|
1313
1272
|
it('writeFiles re-judges the whole batch once every lock is held', async () => {
|
|
1314
|
-
const
|
|
1273
|
+
const versionControl = racingVersionControl(deletePhoenix);
|
|
1315
1274
|
await expectRefusedUnderLock(
|
|
1316
|
-
layer(
|
|
1317
|
-
|
|
1275
|
+
layer(versionControl).writeFiles([{ path: `${KB}/KnowledgeBase/ok.md`, content: 'fine' }, { path: ROLES, content: WITH_PHOENIX }], 'batch'),
|
|
1276
|
+
versionControl,
|
|
1318
1277
|
2,
|
|
1319
1278
|
);
|
|
1320
1279
|
await expect(fs.access(path.join(root, KB, 'KnowledgeBase/ok.md'))).rejects.toBeDefined();
|
|
@@ -1324,31 +1283,31 @@ describe('LockingFilesystem — the agent roles.yaml gate covers every op that l
|
|
|
1324
1283
|
// Appending never adds a role the file had before, so what a race can
|
|
1325
1284
|
// change is validity: the same line appended to the rewritten file breaks it.
|
|
1326
1285
|
const rewritten = 'roles:\n Admin:\n - a@x.eu';
|
|
1327
|
-
const
|
|
1328
|
-
await expect(layer(
|
|
1329
|
-
expect(
|
|
1330
|
-
expect(
|
|
1286
|
+
const versionControl = racingVersionControl(() => fs.writeFile(path.join(root, ROLES), rewritten));
|
|
1287
|
+
await expect(layer(versionControl).appendFile(ROLES, ' - b@x.eu\n')).rejects.toMatchObject({ status: 422, payload: { kind: 'roles-yaml-invalid' } });
|
|
1288
|
+
expect(versionControl.releaseLockUntouched).toHaveBeenCalledTimes(1);
|
|
1289
|
+
expect(versionControl.releaseLockNoCommit).not.toHaveBeenCalled();
|
|
1331
1290
|
expect(await fs.readFile(path.join(root, ROLES), 'utf-8')).toBe(rewritten);
|
|
1332
1291
|
});
|
|
1333
1292
|
|
|
1334
1293
|
it('copyFile judges — and writes — the source bytes read under the lock', async () => {
|
|
1335
1294
|
await fs.writeFile(path.join(root, DRAFT), WITH_PHOENIX);
|
|
1336
|
-
const
|
|
1337
|
-
await expectRefusedUnderLock(layer(
|
|
1295
|
+
const versionControl = racingVersionControl(deletePhoenix);
|
|
1296
|
+
await expectRefusedUnderLock(layer(versionControl).copyFile(DRAFT, ROLES, { overwrite: true }), versionControl, 1);
|
|
1338
1297
|
});
|
|
1339
1298
|
|
|
1340
1299
|
it('moveFile re-judges with both locks held, and both release untouched', async () => {
|
|
1341
1300
|
await fs.writeFile(path.join(root, DRAFT), WITH_PHOENIX);
|
|
1342
|
-
const
|
|
1343
|
-
await expectRefusedUnderLock(layer(
|
|
1301
|
+
const versionControl = racingVersionControl(deletePhoenix);
|
|
1302
|
+
await expectRefusedUnderLock(layer(versionControl).moveFile(DRAFT, ROLES, { overwrite: true }), versionControl, 2);
|
|
1344
1303
|
expect(await fs.readFile(path.join(root, DRAFT), 'utf-8')).toBe(WITH_PHOENIX);
|
|
1345
1304
|
});
|
|
1346
1305
|
|
|
1347
1306
|
it('a members-only write whose roles all survive the wait still lands', async () => {
|
|
1348
|
-
const
|
|
1349
|
-
await layer(
|
|
1307
|
+
const versionControl = racingVersionControl(() => fs.writeFile(path.join(root, ROLES), `${WITH_PHOENIX} - q@x.eu\n`));
|
|
1308
|
+
await layer(versionControl).writeFile(ROLES, `${CURRENT} - b@x.eu\n`);
|
|
1350
1309
|
expect(await fs.readFile(path.join(root, ROLES), 'utf-8')).toBe(`${CURRENT} - b@x.eu\n`);
|
|
1351
|
-
expect(
|
|
1310
|
+
expect(versionControl.releaseLock).toHaveBeenCalledTimes(1);
|
|
1352
1311
|
});
|
|
1353
1312
|
|
|
1354
1313
|
/** A validator that passes before the lock and refuses under it by throwing a bare string. */
|
|
@@ -1363,36 +1322,36 @@ describe('LockingFilesystem — the agent roles.yaml gate covers every op that l
|
|
|
1363
1322
|
}
|
|
1364
1323
|
|
|
1365
1324
|
it('a check that throws a non-Error still releases untouched, and the caller gets that value', async () => {
|
|
1366
|
-
const
|
|
1325
|
+
const versionControl = makeVersionControl();
|
|
1367
1326
|
const fsLayer = new LockingFilesystem(
|
|
1368
1327
|
{ basePath: root, contained: true },
|
|
1369
|
-
{
|
|
1328
|
+
{ versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB, validateWrite: refusesUnderLockWith('refused') },
|
|
1370
1329
|
);
|
|
1371
1330
|
await expect(fsLayer.writeFile(ROLES, CURRENT)).rejects.toBe('refused');
|
|
1372
|
-
expect(
|
|
1373
|
-
expect(
|
|
1331
|
+
expect(versionControl.releaseLockUntouched).toHaveBeenCalledTimes(1);
|
|
1332
|
+
expect(versionControl.releaseLockNoCommit).not.toHaveBeenCalled();
|
|
1374
1333
|
expect(await fs.readFile(path.join(root, ROLES), 'utf-8')).toBe(WITH_PHOENIX);
|
|
1375
1334
|
});
|
|
1376
1335
|
|
|
1377
1336
|
it('a move whose check throws a non-Error releases BOTH locks untouched', async () => {
|
|
1378
|
-
const
|
|
1337
|
+
const versionControl = makeVersionControl();
|
|
1379
1338
|
const fsLayer = new LockingFilesystem(
|
|
1380
1339
|
{ basePath: root, contained: true },
|
|
1381
|
-
{
|
|
1340
|
+
{ versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB, validateWrite: refusesUnderLockWith('refused') },
|
|
1382
1341
|
);
|
|
1383
1342
|
await expect(fsLayer.moveFile(DRAFT, ROLES, { overwrite: true })).rejects.toBe('refused');
|
|
1384
|
-
expect(
|
|
1385
|
-
expect(
|
|
1343
|
+
expect(versionControl.releaseLockUntouched).toHaveBeenCalledTimes(2);
|
|
1344
|
+
expect(versionControl.releaseLockNoCommit).not.toHaveBeenCalled();
|
|
1386
1345
|
expect(await fs.readFile(path.join(root, DRAFT), 'utf-8')).toBe(WITH_PHOENIX);
|
|
1387
1346
|
});
|
|
1388
1347
|
});
|
|
1389
1348
|
|
|
1390
1349
|
it('a copy elsewhere never reads its source for a validator that does not claim the destination', async () => {
|
|
1391
|
-
const
|
|
1350
|
+
const versionControl = makeVersionControl();
|
|
1392
1351
|
const validateWrite = Object.assign(vi.fn(), { appliesTo: vi.fn(() => false) });
|
|
1393
1352
|
const fsLayer = new LockingFilesystem(
|
|
1394
1353
|
{ basePath: root, contained: true },
|
|
1395
|
-
{
|
|
1354
|
+
{ versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB, validateWrite },
|
|
1396
1355
|
);
|
|
1397
1356
|
await fsLayer.copyFile(DRAFT, `${KB}/KnowledgeBase/copy.yaml`);
|
|
1398
1357
|
expect(validateWrite.appliesTo).toHaveBeenCalledWith(`${KB}/KnowledgeBase/copy.yaml`);
|
|
@@ -1421,7 +1380,7 @@ describe('LockingFilesystem — a destination that is taken is refused, never re
|
|
|
1421
1380
|
await fs.writeFile(onDisk(SOURCE), 'source');
|
|
1422
1381
|
fsLayer = new LockingFilesystem(
|
|
1423
1382
|
{ basePath: root, contained: true },
|
|
1424
|
-
{
|
|
1383
|
+
{ versionControl: makeVersionControl(), workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB },
|
|
1425
1384
|
);
|
|
1426
1385
|
});
|
|
1427
1386
|
|
|
@@ -1463,10 +1422,10 @@ describe('LockingFilesystem — a destination that is taken is refused, never re
|
|
|
1463
1422
|
* every lock it unwinds releases untouched.
|
|
1464
1423
|
*/
|
|
1465
1424
|
it('releases UNTOUCHED when the destination is taken, so a lost race cannot eat the winner', async () => {
|
|
1466
|
-
const
|
|
1425
|
+
const versionControl = makeVersionControl();
|
|
1467
1426
|
const layer = new LockingFilesystem(
|
|
1468
1427
|
{ basePath: root, contained: true },
|
|
1469
|
-
{
|
|
1428
|
+
{ versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB },
|
|
1470
1429
|
);
|
|
1471
1430
|
await fs.writeFile(onDisk(TAKEN), 'the winner of the race');
|
|
1472
1431
|
|
|
@@ -1475,10 +1434,10 @@ describe('LockingFilesystem — a destination that is taken is refused, never re
|
|
|
1475
1434
|
// Both ends of the move: the destination is the one holding the winner's
|
|
1476
1435
|
// bytes, and the source was not written either.
|
|
1477
1436
|
for (const p of [SOURCE, TAKEN]) {
|
|
1478
|
-
expect(
|
|
1437
|
+
expect(versionControl.releaseLockUntouched, p).toHaveBeenCalledWith('ws-feat', 'feat', p, USER);
|
|
1479
1438
|
}
|
|
1480
|
-
expect(
|
|
1481
|
-
expect(
|
|
1439
|
+
expect(versionControl.releaseLockNoCommit).not.toHaveBeenCalled();
|
|
1440
|
+
expect(versionControl.releaseLock).not.toHaveBeenCalled();
|
|
1482
1441
|
expect(await fs.readFile(onDisk(TAKEN), 'utf-8')).toBe('the winner of the race');
|
|
1483
1442
|
});
|
|
1484
1443
|
|
|
@@ -1489,10 +1448,10 @@ describe('LockingFilesystem — a destination that is taken is refused, never re
|
|
|
1489
1448
|
* than discarding the winner's queued file.
|
|
1490
1449
|
*/
|
|
1491
1450
|
it('an exclusive write onto a taken name is the same refusal, and releases UNTOUCHED', async () => {
|
|
1492
|
-
const
|
|
1451
|
+
const versionControl = makeVersionControl();
|
|
1493
1452
|
const layer = new LockingFilesystem(
|
|
1494
1453
|
{ basePath: root, contained: true },
|
|
1495
|
-
{
|
|
1454
|
+
{ versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB },
|
|
1496
1455
|
);
|
|
1497
1456
|
await fs.writeFile(onDisk(TAKEN), 'the winner of the race');
|
|
1498
1457
|
|
|
@@ -1501,9 +1460,9 @@ describe('LockingFilesystem — a destination that is taken is refused, never re
|
|
|
1501
1460
|
message: 'A file named taken.md already exists in KnowledgeBase.',
|
|
1502
1461
|
});
|
|
1503
1462
|
|
|
1504
|
-
expect(
|
|
1505
|
-
expect(
|
|
1506
|
-
expect(
|
|
1463
|
+
expect(versionControl.releaseLockUntouched).toHaveBeenCalledWith('ws-feat', 'feat', TAKEN, USER);
|
|
1464
|
+
expect(versionControl.releaseLockNoCommit).not.toHaveBeenCalled();
|
|
1465
|
+
expect(versionControl.releaseLock).not.toHaveBeenCalled();
|
|
1507
1466
|
expect(await fs.readFile(onDisk(TAKEN), 'utf-8')).toBe('the winner of the race');
|
|
1508
1467
|
});
|
|
1509
1468
|
|