@bevel-software/platform-core-backend 0.34.0 → 0.36.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agent-guide/access-control.md +146 -300
- package/agent-guide/conventions.md +23 -25
- package/agent-guide/directory-structure.md +72 -120
- package/agent-guide/drafts-and-change-requests.md +92 -0
- package/agent-guide/for-git-users.md +47 -0
- package/agent-guide/glossary.md +36 -0
- package/agent-guide/how-agents-find-things.md +49 -0
- package/agent-guide/html-views.md +16 -11
- package/agent-guide/new-knowledge-base.md +9 -8
- package/agent-guide/onboarding.md +111 -0
- package/agent-guide/skills.md +67 -42
- package/agent-guide/start-here.md +66 -0
- package/agent-guide/tool-manuals.md +133 -154
- package/agent-guide/tools.md +67 -0
- package/agent-guide/where-a-new-file-goes.md +45 -28
- 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 +27 -25
- package/dist/core/create-core-server.js.map +1 -1
- package/dist/core/create-core-services.d.ts +25 -13
- package/dist/core/create-core-services.d.ts.map +1 -1
- package/dist/core/create-core-services.js +75 -60
- 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.interface.d.ts +22 -0
- package/dist/modules/access/access-control.interface.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.d.ts +3 -1
- package/dist/modules/access/access-control.service.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.js +76 -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 +32 -13
- 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/index.d.ts +1 -1
- package/dist/modules/access/index.d.ts.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 +63 -18
- package/dist/modules/agent-guide/agent-guide.d.ts.map +1 -1
- package/dist/modules/agent-guide/agent-guide.js +144 -30
- package/dist/modules/agent-guide/agent-guide.js.map +1 -1
- package/dist/modules/agent-guide/agent-guide.tools.d.ts +6 -2
- package/dist/modules/agent-guide/agent-guide.tools.d.ts.map +1 -1
- package/dist/modules/agent-guide/agent-guide.tools.js +45 -31
- package/dist/modules/agent-guide/agent-guide.tools.js.map +1 -1
- package/dist/modules/agent-guide/guide-budget.d.ts +51 -0
- package/dist/modules/agent-guide/guide-budget.d.ts.map +1 -0
- package/dist/modules/agent-guide/guide-budget.js +71 -0
- package/dist/modules/agent-guide/guide-budget.js.map +1 -0
- package/dist/modules/agent-guide/index.d.ts +2 -1
- package/dist/modules/agent-guide/index.d.ts.map +1 -1
- package/dist/modules/agent-guide/index.js +2 -1
- package/dist/modules/agent-guide/index.js.map +1 -1
- package/dist/modules/agent-instructions/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 +28 -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 +60 -18
- 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/code-mode/code-mode.tool.js +2 -2
- package/dist/modules/code-mode/code-mode.tool.js.map +1 -1
- package/dist/modules/connection-probe/connection-probe.service.d.ts +0 -12
- package/dist/modules/connection-probe/connection-probe.service.d.ts.map +1 -1
- package/dist/modules/connection-probe/connection-probe.service.js +1 -38
- package/dist/modules/connection-probe/connection-probe.service.js.map +1 -1
- package/dist/modules/database/core-schema.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 +2 -2
- package/dist/modules/kb-fs/locking-filesystem.d.ts +9 -9
- package/dist/modules/kb-fs/locking-filesystem.d.ts.map +1 -1
- package/dist/modules/kb-fs/locking-filesystem.js +27 -27
- 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 +80 -53
- 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 +4 -4
- 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 +87 -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 +635 -74
- package/dist/modules/secrets-vault/db-secrets-vault.service.js.map +1 -1
- package/dist/modules/secrets-vault/index.d.ts +3 -2
- package/dist/modules/secrets-vault/index.d.ts.map +1 -1
- package/dist/modules/secrets-vault/index.js +2 -1
- 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 +205 -11
- package/dist/modules/secrets-vault/secrets-vault.contract.d.ts.map +1 -1
- package/dist/modules/secrets-vault/secrets-vault.contract.js +27 -0
- package/dist/modules/secrets-vault/secrets-vault.contract.js.map +1 -1
- package/dist/modules/secrets-vault/secrets-vault.routes.d.ts +7 -1
- package/dist/modules/secrets-vault/secrets-vault.routes.d.ts.map +1 -1
- package/dist/modules/secrets-vault/secrets-vault.routes.js +206 -45
- 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/oidc-check.d.ts.map +1 -1
- package/dist/modules/settings/oidc-check.js +5 -17
- package/dist/modules/settings/oidc-check.js.map +1 -1
- package/dist/modules/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/skills/skills.tools.js +1 -1
- package/dist/modules/skills/skills.tools.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-def.js +1 -1
- package/dist/modules/tool-helpers/tool-def.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-json-discovery.d.ts +7 -1
- package/dist/modules/tool-manuals/mcp-json-discovery.d.ts.map +1 -1
- package/dist/modules/tool-manuals/mcp-json-discovery.js +69 -38
- package/dist/modules/tool-manuals/mcp-json-discovery.js.map +1 -1
- package/dist/modules/tool-manuals/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 +70 -3
- package/dist/modules/tool-manuals/tool-manuals.contract.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.service.d.ts +18 -8
- package/dist/modules/tool-manuals/tool-manuals.service.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.service.js +166 -49
- package/dist/modules/tool-manuals/tool-manuals.service.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.tools.d.ts +11 -2
- package/dist/modules/tool-manuals/tool-manuals.tools.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.tools.js +66 -24
- 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} +53 -49
- 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 +8 -8
- package/dist/modules/version-control/git/git.service.d.ts.map +1 -0
- package/dist/modules/{workflow → version-control}/git/git.service.js +53 -53
- 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} +4 -4
- 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} +3 -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 -31
- 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} +89 -94
- package/dist/modules/version-control/version-control.service.js.map +1 -0
- package/dist/modules/workspace/agent-access.gate.d.ts +6 -6
- package/dist/modules/workspace/agent-access.gate.d.ts.map +1 -1
- package/dist/modules/workspace/agent-access.gate.js +2 -2
- package/dist/modules/workspace/agent-access.gate.js.map +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 +5 -3
- package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.tools.js +85 -30
- 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/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/oauth-token-error.d.ts +23 -0
- package/dist/shared/oauth-token-error.d.ts.map +1 -0
- package/dist/shared/oauth-token-error.js +47 -0
- package/dist/shared/oauth-token-error.js.map +1 -0
- package/dist/shared/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/shared/read-capped.d.ts +19 -0
- package/dist/shared/read-capped.d.ts.map +1 -0
- package/dist/shared/read-capped.js +45 -0
- package/dist/shared/read-capped.js.map +1 -0
- package/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 +5 -4
- package/src/__tests__/account-admin.ts +27 -0
- package/src/__tests__/hexis-catalog.ts +144 -0
- 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-disk.ts +21 -0
- package/src/__tests__/tokens.ts +19 -0
- package/src/__tests__/tool-handler.ts +15 -0
- package/src/__tests__/tool-manuals.ts +32 -0
- package/src/__tests__/tool-wiring.ts +31 -0
- package/src/__tests__/workspace-service.ts +1 -1
- package/src/core/__tests__/catalog-cache-invalidation.test.ts +3 -3
- 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/__tests__/oauth-sign-in-real-file.test.ts +179 -0
- 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 +40 -26
- package/src/core/create-core-services.ts +92 -66
- 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 +20 -20
- 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 +54 -14
- 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 +27 -27
- package/src/modules/access/__tests__/holder-changes.test.ts +127 -0
- package/src/modules/access/__tests__/roles-admin.security.test.ts +14 -13
- package/src/modules/access/__tests__/roles-admin.service.test.ts +27 -27
- package/src/modules/access/__tests__/roles-capabilities.test.ts +19 -19
- package/src/modules/access/__tests__/roles.routes.test.ts +6 -6
- package/src/modules/access/__tests__/synced-groups-committer.test.ts +13 -13
- package/src/modules/access/__tests__/user-access-removal.service.test.ts +29 -29
- package/src/modules/access/access-control.interface.ts +26 -0
- package/src/modules/access/access-control.service.ts +93 -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 +34 -15
- 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/index.ts +1 -1
- 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 +98 -29
- package/src/modules/agent-guide/__tests__/agent-guide.tools.test.ts +39 -12
- package/src/modules/agent-guide/__tests__/guide-budget.test.ts +95 -0
- package/src/modules/agent-guide/__tests__/guide-eval.test.ts +182 -0
- package/src/modules/agent-guide/__tests__/guide-matches-code.test.ts +263 -0
- package/src/modules/agent-guide/__tests__/tool-surface.json +514 -0
- package/src/modules/agent-guide/__tests__/tool-surface.test.ts +75 -0
- package/src/modules/agent-guide/agent-guide.tools.ts +49 -31
- package/src/modules/agent-guide/agent-guide.ts +165 -32
- package/src/modules/agent-guide/guide-budget.ts +77 -0
- package/src/modules/agent-guide/index.ts +12 -0
- 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 +117 -11
- package/src/modules/agent-instructions/agent-instructions.routes.ts +12 -5
- package/src/modules/agent-instructions/compose.ts +28 -9
- package/src/modules/agent-instructions/shared-file-rules.ts +62 -18
- 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/code-mode/code-mode.tool.ts +2 -2
- package/src/modules/connection-probe/__tests__/connection-probe.service.test.ts +5 -24
- package/src/modules/connection-probe/connection-probe.service.ts +1 -34
- package/src/modules/database/__tests__/advisory-lock.test.ts +169 -169
- package/src/modules/database/__tests__/migrations-unqualified.test.ts +32 -32
- 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 +42 -42
- 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__/locking-filesystem.test.ts +260 -260
- package/src/modules/kb-fs/__tests__/move-with-edits.test.ts +34 -34
- 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 +2 -2
- package/src/modules/kb-fs/locking-filesystem.ts +30 -30
- 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 +93 -62
- package/src/modules/onboarding/__tests__/starter-pack.service.test.ts +55 -55
- 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 +6 -6
- 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__/plugins.routes.test.ts +25 -25
- 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 +458 -4
- package/src/modules/secrets-vault/__tests__/managed-sign-in.route.test.ts +351 -0
- package/src/modules/secrets-vault/__tests__/oauth-live-declaration.route.test.ts +218 -0
- package/src/modules/secrets-vault/__tests__/oauth-return-to.route.test.ts +39 -6
- package/src/modules/secrets-vault/db-secrets-vault.service.ts +728 -118
- package/src/modules/secrets-vault/index.ts +8 -1
- 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 +213 -11
- package/src/modules/secrets-vault/secrets-vault.routes.ts +244 -45
- 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/oidc-check.ts +5 -18
- 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/skills/skills.tools.ts +1 -1
- package/src/modules/tool-helpers/__tests__/agent-roles-write.test.ts +22 -14
- 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__/route-tool-schemas.catalog.test.ts +38 -0
- 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-def.ts +1 -1
- 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.mcp-oauth.test.ts +197 -11
- package/src/modules/tool-manuals/__tests__/tool-manuals.restart.test.ts +1 -1
- package/src/modules/tool-manuals/__tests__/tool-manuals.tools.test.ts +132 -6
- package/src/modules/tool-manuals/mcp-json-discovery.ts +73 -34
- 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 +63 -3
- package/src/modules/tool-manuals/tool-manuals.service.ts +191 -51
- package/src/modules/tool-manuals/tool-manuals.tools.ts +93 -28
- 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 +12 -119
- 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} +97 -99
- 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} +42 -42
- 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} +52 -48
- 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/{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 +20 -20
- 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 +57 -57
- 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} +3 -3
- 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} +92 -97
- 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 +12 -14
- package/src/modules/workspace/__tests__/workspace.tools.branch-errors.test.ts +5 -5
- package/src/modules/workspace/__tests__/workspace.tools.test.ts +182 -75
- package/src/modules/workspace/agent-access.gate.ts +6 -6
- 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 +90 -30
- package/src/shared/__tests__/canonical-file-identity.test.ts +5 -5
- package/src/shared/__tests__/http-errors.test.ts +4 -4
- package/src/shared/__tests__/read-capped.test.ts +68 -0
- package/src/shared/canonical-file-identity.ts +5 -5
- package/src/shared/domain-errors.ts +44 -44
- 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/oauth-token-error.ts +46 -0
- package/src/shared/pending-proposals.ts +4 -4
- package/src/shared/read-capped.ts +40 -0
- 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/agent-guide/finding-things.md +0 -7
- package/agent-guide/introduction.md +0 -27
- 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,10 +2,10 @@ 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,
|
|
8
|
+
import { PushNeedsAgentResolutionError, VersionControlValidationError } from '../../../shared/domain-errors.js';
|
|
9
9
|
import {
|
|
10
10
|
makeAgentRolesYamlWriteValidator,
|
|
11
11
|
RolesYamlInvalidError,
|
|
@@ -21,7 +21,7 @@ const USER: AuthUser = {
|
|
|
21
21
|
name: 'Alice',
|
|
22
22
|
};
|
|
23
23
|
|
|
24
|
-
function
|
|
24
|
+
function makeVersionControl(): IVersionControlService {
|
|
25
25
|
return {
|
|
26
26
|
acquireLock: vi.fn().mockResolvedValue({
|
|
27
27
|
acquired: true,
|
|
@@ -42,7 +42,7 @@ function makeWorkflow(): IWorkflowService {
|
|
|
42
42
|
getLock: vi.fn(),
|
|
43
43
|
commitFileWhileLocked: vi.fn(),
|
|
44
44
|
commitChanges: vi.fn().mockResolvedValue({}),
|
|
45
|
-
} as unknown as
|
|
45
|
+
} as unknown as IVersionControlService;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
async function mkTmpRoot(): Promise<string> {
|
|
@@ -61,29 +61,29 @@ describe('LockingFilesystem — every mutating op runs acquire → super → rel
|
|
|
61
61
|
});
|
|
62
62
|
|
|
63
63
|
it('writeFile acquires, writes through to disk, then releases', async () => {
|
|
64
|
-
const
|
|
64
|
+
const versionControl = makeVersionControl();
|
|
65
65
|
const fsLayer = new LockingFilesystem(
|
|
66
66
|
{ basePath: root, contained: true },
|
|
67
|
-
{
|
|
67
|
+
{ versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB },
|
|
68
68
|
);
|
|
69
69
|
await fsLayer.writeFile('knowledge-base/Knowledge/Foo.md', 'hello\n');
|
|
70
70
|
|
|
71
|
-
expect(
|
|
71
|
+
expect(versionControl.acquireLock).toHaveBeenCalledWith(
|
|
72
72
|
'ws-feat',
|
|
73
73
|
'feat',
|
|
74
74
|
'knowledge-base/Knowledge/Foo.md',
|
|
75
75
|
USER,
|
|
76
76
|
);
|
|
77
|
-
expect(
|
|
77
|
+
expect(versionControl.releaseLock).toHaveBeenCalledWith(
|
|
78
78
|
'ws-feat',
|
|
79
79
|
'feat',
|
|
80
80
|
'knowledge-base/Knowledge/Foo.md',
|
|
81
81
|
USER,
|
|
82
82
|
);
|
|
83
83
|
// Acquire fires before release.
|
|
84
|
-
const acquireOrder = (
|
|
84
|
+
const acquireOrder = (versionControl.acquireLock as ReturnType<typeof vi.fn>).mock
|
|
85
85
|
.invocationCallOrder[0];
|
|
86
|
-
const releaseOrder = (
|
|
86
|
+
const releaseOrder = (versionControl.releaseLock as ReturnType<typeof vi.fn>).mock
|
|
87
87
|
.invocationCallOrder[0];
|
|
88
88
|
expect(acquireOrder).toBeLessThan(releaseOrder);
|
|
89
89
|
// The bytes actually landed on disk.
|
|
@@ -94,33 +94,33 @@ describe('LockingFilesystem — every mutating op runs acquire → super → rel
|
|
|
94
94
|
const filePath = path.join(root, 'knowledge-base/a.md');
|
|
95
95
|
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
96
96
|
await fs.writeFile(filePath, 'x', 'utf-8');
|
|
97
|
-
const
|
|
97
|
+
const versionControl = makeVersionControl();
|
|
98
98
|
const fsLayer = new LockingFilesystem(
|
|
99
99
|
{ basePath: root, contained: true },
|
|
100
|
-
{
|
|
100
|
+
{ versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB },
|
|
101
101
|
);
|
|
102
102
|
await fsLayer.deleteFile('knowledge-base/a.md');
|
|
103
|
-
expect(
|
|
104
|
-
expect(
|
|
103
|
+
expect(versionControl.acquireLock).toHaveBeenCalledWith('ws-feat', 'feat', 'knowledge-base/a.md', USER);
|
|
104
|
+
expect(versionControl.releaseLock).toHaveBeenCalledWith('ws-feat', 'feat', 'knowledge-base/a.md', USER);
|
|
105
105
|
await expect(fs.access(filePath)).rejects.toBeDefined();
|
|
106
106
|
});
|
|
107
107
|
|
|
108
108
|
it('mkdir drops a .gitkeep through the lock-aware writeFile so the empty folder is committed', async () => {
|
|
109
|
-
const
|
|
109
|
+
const versionControl = makeVersionControl();
|
|
110
110
|
const fsLayer = new LockingFilesystem(
|
|
111
111
|
{ basePath: root, contained: true },
|
|
112
|
-
{
|
|
112
|
+
{ versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB },
|
|
113
113
|
);
|
|
114
114
|
await fsLayer.mkdir('knowledge-base/empty');
|
|
115
115
|
// .gitkeep got written through the lock path. The lock is on the
|
|
116
116
|
// .gitkeep path (that's the file the commit attaches to).
|
|
117
|
-
expect(
|
|
117
|
+
expect(versionControl.acquireLock).toHaveBeenCalledWith(
|
|
118
118
|
'ws-feat',
|
|
119
119
|
'feat',
|
|
120
120
|
'knowledge-base/empty/.gitkeep',
|
|
121
121
|
USER,
|
|
122
122
|
);
|
|
123
|
-
expect(
|
|
123
|
+
expect(versionControl.releaseLock).toHaveBeenCalledWith(
|
|
124
124
|
'ws-feat',
|
|
125
125
|
'feat',
|
|
126
126
|
'knowledge-base/empty/.gitkeep',
|
|
@@ -132,37 +132,37 @@ describe('LockingFilesystem — every mutating op runs acquire → super → rel
|
|
|
132
132
|
it('mkdir of an already-populated dir does NOT drop a .gitkeep', async () => {
|
|
133
133
|
await fs.mkdir(path.join(root, 'knowledge-base/has-content'), { recursive: true });
|
|
134
134
|
await fs.writeFile(path.join(root, 'knowledge-base/has-content/real.md'), 'hi', 'utf-8');
|
|
135
|
-
const
|
|
135
|
+
const versionControl = makeVersionControl();
|
|
136
136
|
const fsLayer = new LockingFilesystem(
|
|
137
137
|
{ basePath: root, contained: true },
|
|
138
|
-
{
|
|
138
|
+
{ versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB },
|
|
139
139
|
);
|
|
140
140
|
await fsLayer.mkdir('knowledge-base/has-content', { recursive: true });
|
|
141
|
-
expect(
|
|
142
|
-
expect(
|
|
141
|
+
expect(versionControl.acquireLock).not.toHaveBeenCalled();
|
|
142
|
+
expect(versionControl.releaseLock).not.toHaveBeenCalled();
|
|
143
143
|
// The existing file is untouched.
|
|
144
144
|
expect(await fs.readFile(path.join(root, 'knowledge-base/has-content/real.md'), 'utf-8')).toBe('hi');
|
|
145
145
|
});
|
|
146
146
|
|
|
147
147
|
it('rmdir is refused with a clear error — one-change-per-file invariant', async () => {
|
|
148
148
|
await fs.mkdir(path.join(root, 'knowledge-base/doomed'), { recursive: true });
|
|
149
|
-
const
|
|
149
|
+
const versionControl = makeVersionControl();
|
|
150
150
|
const fsLayer = new LockingFilesystem(
|
|
151
151
|
{ basePath: root, contained: true },
|
|
152
|
-
{
|
|
152
|
+
{ versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB },
|
|
153
153
|
);
|
|
154
154
|
await expect(fsLayer.rmdir('knowledge-base/doomed')).rejects.toThrow(
|
|
155
155
|
/Recursive directory removal is not supported/i,
|
|
156
156
|
);
|
|
157
|
-
expect(
|
|
157
|
+
expect(versionControl.acquireLock).not.toHaveBeenCalled();
|
|
158
158
|
});
|
|
159
159
|
|
|
160
160
|
it(
|
|
161
161
|
'retries acquire on contention then surfaces a skip error after 3 attempts',
|
|
162
162
|
async () => {
|
|
163
163
|
// 3 attempts × 2s ≈ 6s — bump vitest's default timeout for this one test.
|
|
164
|
-
const
|
|
165
|
-
(
|
|
164
|
+
const versionControl = makeVersionControl();
|
|
165
|
+
(versionControl.acquireLock as ReturnType<typeof vi.fn>).mockResolvedValue({
|
|
166
166
|
acquired: false,
|
|
167
167
|
lock: {
|
|
168
168
|
branch: 'feat',
|
|
@@ -176,23 +176,23 @@ describe('LockingFilesystem — every mutating op runs acquire → super → rel
|
|
|
176
176
|
});
|
|
177
177
|
const fsLayer = new LockingFilesystem(
|
|
178
178
|
{ basePath: root, contained: true },
|
|
179
|
-
{
|
|
179
|
+
{ versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB },
|
|
180
180
|
);
|
|
181
181
|
|
|
182
182
|
await expect(fsLayer.writeFile('knowledge-base/Locked.md', 'x')).rejects.toThrow(
|
|
183
183
|
/Skipped editing "knowledge-base\/Locked\.md" — locked by Bob/,
|
|
184
184
|
);
|
|
185
|
-
expect(
|
|
186
|
-
expect(
|
|
185
|
+
expect(versionControl.acquireLock).toHaveBeenCalledTimes(3);
|
|
186
|
+
expect(versionControl.releaseLock).not.toHaveBeenCalled();
|
|
187
187
|
},
|
|
188
188
|
10_000,
|
|
189
189
|
);
|
|
190
190
|
|
|
191
191
|
it('drops the lock without committing when the underlying write throws', async () => {
|
|
192
|
-
const
|
|
192
|
+
const versionControl = makeVersionControl();
|
|
193
193
|
const fsLayer = new LockingFilesystem(
|
|
194
194
|
{ basePath: root, contained: true },
|
|
195
|
-
{
|
|
195
|
+
{ versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB },
|
|
196
196
|
);
|
|
197
197
|
// The underlying write dies (disk full, permissions, whatever). The lock
|
|
198
198
|
// must still go away so the next caller can edit a different file, but
|
|
@@ -207,9 +207,9 @@ describe('LockingFilesystem — every mutating op runs acquire → super → rel
|
|
|
207
207
|
} finally {
|
|
208
208
|
spy.mockRestore();
|
|
209
209
|
}
|
|
210
|
-
expect(
|
|
211
|
-
expect(
|
|
212
|
-
expect(
|
|
210
|
+
expect(versionControl.acquireLock).toHaveBeenCalled();
|
|
211
|
+
expect(versionControl.releaseLockNoCommit).toHaveBeenCalled();
|
|
212
|
+
expect(versionControl.releaseLock).not.toHaveBeenCalled();
|
|
213
213
|
});
|
|
214
214
|
});
|
|
215
215
|
|
|
@@ -228,12 +228,12 @@ describe('LockingFilesystem.writeFiles — batch with deletes + one batched chan
|
|
|
228
228
|
await fs.mkdir(path.join(root, 'knowledge-base/Tools'), { recursive: true });
|
|
229
229
|
await fs.writeFile(path.join(root, 'knowledge-base/Tools/old.tool'), '{}');
|
|
230
230
|
|
|
231
|
-
const
|
|
231
|
+
const versionControl = makeVersionControl();
|
|
232
232
|
const emitted: unknown[] = [];
|
|
233
233
|
const fsLayer = new LockingFilesystem(
|
|
234
234
|
{ basePath: root, contained: true },
|
|
235
235
|
{
|
|
236
|
-
|
|
236
|
+
versionControl,
|
|
237
237
|
workspaceId: 'ws-feat',
|
|
238
238
|
branch: 'feat',
|
|
239
239
|
user: USER, kbDirName: KB,
|
|
@@ -248,8 +248,8 @@ describe('LockingFilesystem.writeFiles — batch with deletes + one batched chan
|
|
|
248
248
|
);
|
|
249
249
|
|
|
250
250
|
// Both paths locked; both mutations landed; commit returned.
|
|
251
|
-
expect(
|
|
252
|
-
expect(
|
|
251
|
+
expect(versionControl.acquireLock).toHaveBeenCalledWith('ws-feat', 'feat', 'knowledge-base/Tools/new.tool', USER);
|
|
252
|
+
expect(versionControl.acquireLock).toHaveBeenCalledWith('ws-feat', 'feat', 'knowledge-base/Tools/old.tool', USER);
|
|
253
253
|
expect(await fs.readFile(path.join(root, 'knowledge-base/Tools/new.tool'), 'utf-8')).toContain('http');
|
|
254
254
|
await expect(fs.access(path.join(root, 'knowledge-base/Tools/old.tool'))).rejects.toThrow();
|
|
255
255
|
expect(change).toEqual({});
|
|
@@ -267,13 +267,13 @@ describe('LockingFilesystem.writeFiles — batch with deletes + one batched chan
|
|
|
267
267
|
await fs.mkdir(path.join(root, 'knowledge-base/Tools'), { recursive: true });
|
|
268
268
|
await fs.writeFile(path.join(root, 'knowledge-base/Tools/old.tool'), '{}');
|
|
269
269
|
|
|
270
|
-
const
|
|
271
|
-
(
|
|
270
|
+
const versionControl = makeVersionControl();
|
|
271
|
+
(versionControl.commitChanges as ReturnType<typeof vi.fn>).mockRejectedValue(new Error('push exploded'));
|
|
272
272
|
const emitted: unknown[] = [];
|
|
273
273
|
const fsLayer = new LockingFilesystem(
|
|
274
274
|
{ basePath: root, contained: true },
|
|
275
275
|
{
|
|
276
|
-
|
|
276
|
+
versionControl,
|
|
277
277
|
workspaceId: 'ws-feat',
|
|
278
278
|
branch: 'feat',
|
|
279
279
|
user: USER, kbDirName: KB,
|
|
@@ -291,11 +291,11 @@ describe('LockingFilesystem.writeFiles — batch with deletes + one batched chan
|
|
|
291
291
|
|
|
292
292
|
// Every acquired path goes through the NO-COMMIT release — which is what
|
|
293
293
|
// discards the uncommitted bytes (write reverted, delete restored) in the
|
|
294
|
-
// real
|
|
294
|
+
// real VersionControlService via git.discardPath. The committing release must
|
|
295
295
|
// never run, and no change event fires for a failed batch.
|
|
296
|
-
expect(
|
|
297
|
-
expect(
|
|
298
|
-
expect(
|
|
296
|
+
expect(versionControl.releaseLockNoCommit).toHaveBeenCalledWith('ws-feat', 'feat', 'knowledge-base/Tools/new.tool', USER);
|
|
297
|
+
expect(versionControl.releaseLockNoCommit).toHaveBeenCalledWith('ws-feat', 'feat', 'knowledge-base/Tools/old.tool', USER);
|
|
298
|
+
expect(versionControl.releaseLock).not.toHaveBeenCalled();
|
|
299
299
|
expect(emitted).toHaveLength(0);
|
|
300
300
|
});
|
|
301
301
|
|
|
@@ -307,15 +307,15 @@ describe('LockingFilesystem.writeFiles — batch with deletes + one batched chan
|
|
|
307
307
|
// forever. The batch must releaseLock (the enqueued release commit no-ops
|
|
308
308
|
// on the clean tree and the worker's unpushed-commits check re-runs the
|
|
309
309
|
// push ladder) and still propagate the error.
|
|
310
|
-
const
|
|
311
|
-
(
|
|
310
|
+
const versionControl = makeVersionControl();
|
|
311
|
+
(versionControl.commitChanges as ReturnType<typeof vi.fn>).mockRejectedValue(
|
|
312
312
|
new PushNeedsAgentResolutionError('feat', 'knowledge-base/A.md', 'non-fast-forward', 'rebase failed'),
|
|
313
313
|
);
|
|
314
314
|
const emitted: unknown[] = [];
|
|
315
315
|
const fsLayer = new LockingFilesystem(
|
|
316
316
|
{ basePath: root, contained: true },
|
|
317
317
|
{
|
|
318
|
-
|
|
318
|
+
versionControl,
|
|
319
319
|
workspaceId: 'ws-feat',
|
|
320
320
|
branch: 'feat',
|
|
321
321
|
user: USER, kbDirName: KB,
|
|
@@ -327,19 +327,19 @@ describe('LockingFilesystem.writeFiles — batch with deletes + one batched chan
|
|
|
327
327
|
fsLayer.writeFiles([{ path: 'knowledge-base/A.md', content: 'x' }], 'batch'),
|
|
328
328
|
).rejects.toBeInstanceOf(PushNeedsAgentResolutionError);
|
|
329
329
|
|
|
330
|
-
expect(
|
|
331
|
-
expect(
|
|
330
|
+
expect(versionControl.releaseLock).toHaveBeenCalledWith('ws-feat', 'feat', 'knowledge-base/A.md', USER);
|
|
331
|
+
expect(versionControl.releaseLockNoCommit).not.toHaveBeenCalled();
|
|
332
332
|
expect(emitted).toHaveLength(0);
|
|
333
333
|
});
|
|
334
334
|
|
|
335
335
|
it('emits nothing on a no-op commit (clean tree)', async () => {
|
|
336
|
-
const
|
|
337
|
-
(
|
|
336
|
+
const versionControl = makeVersionControl();
|
|
337
|
+
(versionControl.commitChanges as ReturnType<typeof vi.fn>).mockResolvedValue(null);
|
|
338
338
|
const emitted: unknown[] = [];
|
|
339
339
|
const fsLayer = new LockingFilesystem(
|
|
340
340
|
{ basePath: root, contained: true },
|
|
341
341
|
{
|
|
342
|
-
|
|
342
|
+
versionControl,
|
|
343
343
|
workspaceId: 'ws-feat',
|
|
344
344
|
branch: 'feat',
|
|
345
345
|
user: USER, kbDirName: KB,
|
|
@@ -362,24 +362,24 @@ describe('LockingFilesystem — the caller judges under the lock', () => {
|
|
|
362
362
|
await fs.rm(root, { recursive: true, force: true });
|
|
363
363
|
});
|
|
364
364
|
|
|
365
|
-
const layer = (
|
|
365
|
+
const layer = (versionControl: IVersionControlService): LockingFilesystem =>
|
|
366
366
|
new LockingFilesystem(
|
|
367
367
|
{ basePath: root, contained: true },
|
|
368
|
-
{
|
|
368
|
+
{ versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB },
|
|
369
369
|
);
|
|
370
370
|
|
|
371
371
|
it('writeFile runs the check with the lock HELD and before any byte lands', async () => {
|
|
372
372
|
await fs.mkdir(path.join(root, 'knowledge-base'), { recursive: true });
|
|
373
373
|
await fs.writeFile(path.join(root, 'knowledge-base/Foo.md'), 'before\n');
|
|
374
374
|
|
|
375
|
-
const
|
|
375
|
+
const versionControl = makeVersionControl();
|
|
376
376
|
const order: string[] = [];
|
|
377
|
-
(
|
|
377
|
+
(versionControl.acquireLock as ReturnType<typeof vi.fn>).mockImplementation(async () => {
|
|
378
378
|
order.push('acquire');
|
|
379
379
|
return { acquired: true, lock: { holderName: 'Alice' } };
|
|
380
380
|
});
|
|
381
381
|
|
|
382
|
-
await layer(
|
|
382
|
+
await layer(versionControl).writeFile('knowledge-base/Foo.md', 'after\n', undefined, async () => {
|
|
383
383
|
order.push('check');
|
|
384
384
|
// The check reads the state the write is about to replace.
|
|
385
385
|
expect(await fs.readFile(path.join(root, 'knowledge-base/Foo.md'), 'utf-8')).toBe('before\n');
|
|
@@ -393,10 +393,10 @@ describe('LockingFilesystem — the caller judges under the lock', () => {
|
|
|
393
393
|
await fs.mkdir(path.join(root, 'knowledge-base'), { recursive: true });
|
|
394
394
|
await fs.writeFile(path.join(root, 'knowledge-base/Foo.md'), 'theirs\n');
|
|
395
395
|
|
|
396
|
-
const
|
|
396
|
+
const versionControl = makeVersionControl();
|
|
397
397
|
const refusal = new Error('already exists');
|
|
398
398
|
await expect(
|
|
399
|
-
layer(
|
|
399
|
+
layer(versionControl).writeFile('knowledge-base/Foo.md', 'mine\n', undefined, () => {
|
|
400
400
|
throw refusal;
|
|
401
401
|
}),
|
|
402
402
|
).rejects.toBe(refusal);
|
|
@@ -404,30 +404,30 @@ describe('LockingFilesystem — the caller judges under the lock', () => {
|
|
|
404
404
|
expect(await fs.readFile(path.join(root, 'knowledge-base/Foo.md'), 'utf-8')).toBe('theirs\n');
|
|
405
405
|
// Untouched, never no-commit: a discard would reset the path to HEAD and
|
|
406
406
|
// destroy whatever save left those bytes there.
|
|
407
|
-
expect(
|
|
408
|
-
expect(
|
|
409
|
-
expect(
|
|
407
|
+
expect(versionControl.releaseLockUntouched).toHaveBeenCalledWith('ws-feat', 'feat', 'knowledge-base/Foo.md', USER);
|
|
408
|
+
expect(versionControl.releaseLockNoCommit).not.toHaveBeenCalled();
|
|
409
|
+
expect(versionControl.releaseLock).not.toHaveBeenCalled();
|
|
410
410
|
});
|
|
411
411
|
|
|
412
412
|
it('rewriteFile reads, computes and writes with the lock HELD, from the bytes on disk at that moment', async () => {
|
|
413
413
|
await fs.mkdir(path.join(root, 'knowledge-base'), { recursive: true });
|
|
414
414
|
await fs.writeFile(path.join(root, 'knowledge-base/Foo.md'), 'owner: \n');
|
|
415
415
|
|
|
416
|
-
const
|
|
416
|
+
const versionControl = makeVersionControl();
|
|
417
417
|
const order: string[] = [];
|
|
418
|
-
(
|
|
418
|
+
(versionControl.acquireLock as ReturnType<typeof vi.fn>).mockImplementation(async () => {
|
|
419
419
|
order.push('acquire');
|
|
420
420
|
// Another writer lands while this call waits for the lock.
|
|
421
421
|
await fs.writeFile(path.join(root, 'knowledge-base/Foo.md'), 'owner: alice\n');
|
|
422
422
|
return { acquired: true, lock: { holderName: 'Alice' } };
|
|
423
423
|
});
|
|
424
|
-
(
|
|
424
|
+
(versionControl.releaseLock as ReturnType<typeof vi.fn>).mockImplementation(async () => {
|
|
425
425
|
order.push('release');
|
|
426
426
|
return null;
|
|
427
427
|
});
|
|
428
428
|
|
|
429
429
|
let seen = '';
|
|
430
|
-
await layer(
|
|
430
|
+
await layer(versionControl).rewriteFile('knowledge-base/Foo.md', (current) => {
|
|
431
431
|
order.push('rewrite');
|
|
432
432
|
seen = current!.toString('utf8');
|
|
433
433
|
return `${seen}note: kept\n`;
|
|
@@ -437,32 +437,32 @@ describe('LockingFilesystem — the caller judges under the lock', () => {
|
|
|
437
437
|
// It was handed what the other writer left, not what was there when the call began.
|
|
438
438
|
expect(seen).toBe('owner: alice\n');
|
|
439
439
|
expect(await fs.readFile(path.join(root, 'knowledge-base/Foo.md'), 'utf-8')).toBe('owner: alice\nnote: kept\n');
|
|
440
|
-
expect(
|
|
440
|
+
expect(versionControl.releaseLock).toHaveBeenCalledWith('ws-feat', 'feat', 'knowledge-base/Foo.md', USER);
|
|
441
441
|
});
|
|
442
442
|
|
|
443
443
|
it('a rewriteFile that throws writes nothing, releases UNTOUCHED, and gives the caller its own error', async () => {
|
|
444
444
|
await fs.mkdir(path.join(root, 'knowledge-base'), { recursive: true });
|
|
445
445
|
await fs.writeFile(path.join(root, 'knowledge-base/Foo.md'), 'theirs\n');
|
|
446
446
|
|
|
447
|
-
const
|
|
447
|
+
const versionControl = makeVersionControl();
|
|
448
448
|
const refusal = new Error('old text is gone');
|
|
449
449
|
await expect(
|
|
450
|
-
layer(
|
|
450
|
+
layer(versionControl).rewriteFile('knowledge-base/Foo.md', () => {
|
|
451
451
|
throw refusal;
|
|
452
452
|
}),
|
|
453
453
|
).rejects.toBe(refusal);
|
|
454
454
|
|
|
455
455
|
expect(await fs.readFile(path.join(root, 'knowledge-base/Foo.md'), 'utf-8')).toBe('theirs\n');
|
|
456
|
-
expect(
|
|
457
|
-
expect(
|
|
458
|
-
expect(
|
|
456
|
+
expect(versionControl.releaseLockUntouched).toHaveBeenCalledWith('ws-feat', 'feat', 'knowledge-base/Foo.md', USER);
|
|
457
|
+
expect(versionControl.releaseLockNoCommit).not.toHaveBeenCalled();
|
|
458
|
+
expect(versionControl.releaseLock).not.toHaveBeenCalled();
|
|
459
459
|
});
|
|
460
460
|
|
|
461
461
|
it('rewriteFile hands over null when nothing is at the path', async () => {
|
|
462
462
|
await fs.mkdir(path.join(root, 'knowledge-base'), { recursive: true });
|
|
463
|
-
const
|
|
463
|
+
const versionControl = makeVersionControl();
|
|
464
464
|
let seen: Buffer | null | undefined;
|
|
465
|
-
await layer(
|
|
465
|
+
await layer(versionControl).rewriteFile('knowledge-base/New.md', (current) => {
|
|
466
466
|
seen = current;
|
|
467
467
|
return 'made\n';
|
|
468
468
|
});
|
|
@@ -473,13 +473,13 @@ describe('LockingFilesystem — the caller judges under the lock', () => {
|
|
|
473
473
|
it('rewriteFile judges the bytes it is about to write with the pre-disk validator, under the lock', async () => {
|
|
474
474
|
await fs.mkdir(path.join(root, 'knowledge-base'), { recursive: true });
|
|
475
475
|
await fs.writeFile(path.join(root, 'knowledge-base/Foo.md'), 'ok\n');
|
|
476
|
-
const
|
|
476
|
+
const versionControl = makeVersionControl();
|
|
477
477
|
const refusal = new Error('refused by the validator');
|
|
478
478
|
const judged: string[] = [];
|
|
479
479
|
const guarded = new LockingFilesystem(
|
|
480
480
|
{ basePath: root, contained: true },
|
|
481
481
|
{
|
|
482
|
-
|
|
482
|
+
versionControl,
|
|
483
483
|
workspaceId: 'ws-feat',
|
|
484
484
|
branch: 'feat',
|
|
485
485
|
user: USER,
|
|
@@ -493,19 +493,19 @@ describe('LockingFilesystem — the caller judges under the lock', () => {
|
|
|
493
493
|
await expect(guarded.rewriteFile('knowledge-base/Foo.md', () => 'bad\n')).rejects.toBe(refusal);
|
|
494
494
|
expect(judged).toEqual(['bad\n']);
|
|
495
495
|
expect(await fs.readFile(path.join(root, 'knowledge-base/Foo.md'), 'utf-8')).toBe('ok\n');
|
|
496
|
-
expect(
|
|
496
|
+
expect(versionControl.releaseLockUntouched).toHaveBeenCalled();
|
|
497
497
|
});
|
|
498
498
|
|
|
499
499
|
it('rewriteFile refuses a path outside the repository before any lock', async () => {
|
|
500
|
-
const
|
|
501
|
-
await expect(layer(
|
|
502
|
-
expect(
|
|
500
|
+
const versionControl = makeVersionControl();
|
|
501
|
+
await expect(layer(versionControl).rewriteFile('elsewhere/Foo.md', () => 'x')).rejects.toThrow();
|
|
502
|
+
expect(versionControl.acquireLock).not.toHaveBeenCalled();
|
|
503
503
|
});
|
|
504
504
|
|
|
505
505
|
it('writeFiles runs its check once EVERY lock is held, and lands only what the check keeps', async () => {
|
|
506
|
-
const
|
|
506
|
+
const versionControl = makeVersionControl();
|
|
507
507
|
const acquired: string[] = [];
|
|
508
|
-
(
|
|
508
|
+
(versionControl.acquireLock as ReturnType<typeof vi.fn>).mockImplementation(async (_w, _b, p: string) => {
|
|
509
509
|
acquired.push(p);
|
|
510
510
|
return { acquired: true, lock: { holderName: 'Alice' } };
|
|
511
511
|
});
|
|
@@ -515,7 +515,7 @@ describe('LockingFilesystem — the caller judges under the lock', () => {
|
|
|
515
515
|
{ path: 'knowledge-base/b.md', content: 'B' },
|
|
516
516
|
{ path: 'knowledge-base/c.md', content: 'C' },
|
|
517
517
|
];
|
|
518
|
-
await layer(
|
|
518
|
+
await layer(versionControl).writeFiles(writes, 'batch', [], async (pending) => {
|
|
519
519
|
// Every path of the batch is locked before the check gets to judge any.
|
|
520
520
|
expect([...acquired].sort()).toEqual(['knowledge-base/a.md', 'knowledge-base/b.md', 'knowledge-base/c.md']);
|
|
521
521
|
expect(pending.map((w) => w.path)).toEqual(writes.map((w) => w.path));
|
|
@@ -528,16 +528,16 @@ describe('LockingFilesystem — the caller judges under the lock', () => {
|
|
|
528
528
|
// The dropped path stays OUT of the commit's scope — an unscoped commit
|
|
529
529
|
// there would sweep in whatever another save left dirty on it — and
|
|
530
530
|
// releases untouched, because this batch never wrote a byte to it.
|
|
531
|
-
expect(
|
|
531
|
+
expect(versionControl.commitChanges).toHaveBeenCalledWith('ws-feat', USER, 'batch', [
|
|
532
532
|
'knowledge-base/a.md',
|
|
533
533
|
'knowledge-base/c.md',
|
|
534
534
|
]);
|
|
535
|
-
expect(
|
|
535
|
+
expect(versionControl.releaseLockUntouched).toHaveBeenCalledWith('ws-feat', 'feat', 'knowledge-base/b.md', USER);
|
|
536
536
|
});
|
|
537
537
|
|
|
538
538
|
it('a writeFiles check that drops EVERY write commits nothing and releases everything untouched', async () => {
|
|
539
|
-
const
|
|
540
|
-
const change = await layer(
|
|
539
|
+
const versionControl = makeVersionControl();
|
|
540
|
+
const change = await layer(versionControl).writeFiles(
|
|
541
541
|
[{ path: 'knowledge-base/a.md', content: 'A' }],
|
|
542
542
|
'batch',
|
|
543
543
|
[],
|
|
@@ -546,15 +546,15 @@ describe('LockingFilesystem — the caller judges under the lock', () => {
|
|
|
546
546
|
|
|
547
547
|
expect(change).toBeNull();
|
|
548
548
|
await expect(fs.access(path.join(root, 'knowledge-base/a.md'))).rejects.toThrow();
|
|
549
|
-
expect(
|
|
550
|
-
expect(
|
|
549
|
+
expect(versionControl.commitChanges).not.toHaveBeenCalled();
|
|
550
|
+
expect(versionControl.releaseLockUntouched).toHaveBeenCalledWith('ws-feat', 'feat', 'knowledge-base/a.md', USER);
|
|
551
551
|
});
|
|
552
552
|
|
|
553
553
|
it('a writeFiles check that THROWS refuses the whole batch, untouched', async () => {
|
|
554
|
-
const
|
|
554
|
+
const versionControl = makeVersionControl();
|
|
555
555
|
const refusal = new Error('nope');
|
|
556
556
|
await expect(
|
|
557
|
-
layer(
|
|
557
|
+
layer(versionControl).writeFiles(
|
|
558
558
|
[{ path: 'knowledge-base/a.md', content: 'A' }, { path: 'knowledge-base/b.md', content: 'B' }],
|
|
559
559
|
'batch',
|
|
560
560
|
[],
|
|
@@ -565,8 +565,8 @@ describe('LockingFilesystem — the caller judges under the lock', () => {
|
|
|
565
565
|
).rejects.toBe(refusal);
|
|
566
566
|
|
|
567
567
|
await expect(fs.access(path.join(root, 'knowledge-base/a.md'))).rejects.toThrow();
|
|
568
|
-
expect(
|
|
569
|
-
expect(
|
|
568
|
+
expect(versionControl.commitChanges).not.toHaveBeenCalled();
|
|
569
|
+
expect(versionControl.releaseLockUntouched).toHaveBeenCalledTimes(2);
|
|
570
570
|
});
|
|
571
571
|
});
|
|
572
572
|
|
|
@@ -589,11 +589,11 @@ describe('LockingFilesystem — creator read grants on creation', () => {
|
|
|
589
589
|
}
|
|
590
590
|
|
|
591
591
|
it('writeFile asks the planner about the new file and lands the content untouched', async () => {
|
|
592
|
-
const
|
|
592
|
+
const versionControl = makeVersionControl();
|
|
593
593
|
const creatorAccess = makeCreatorAccess();
|
|
594
594
|
const fsLayer = new LockingFilesystem(
|
|
595
595
|
{ basePath: root, contained: true },
|
|
596
|
-
{
|
|
596
|
+
{ versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB, creatorAccess },
|
|
597
597
|
);
|
|
598
598
|
await fsLayer.writeFile('knowledge-base/KnowledgeBase/new.md', '# New\n');
|
|
599
599
|
expect(creatorAccess.planForCreate).toHaveBeenCalledWith(
|
|
@@ -608,7 +608,7 @@ describe('LockingFilesystem — creator read grants on creation', () => {
|
|
|
608
608
|
});
|
|
609
609
|
|
|
610
610
|
it('writeFile seeds a subtree access.md (own lock cycle) before the file itself', async () => {
|
|
611
|
-
const
|
|
611
|
+
const versionControl = makeVersionControl();
|
|
612
612
|
const creatorAccess = makeCreatorAccess();
|
|
613
613
|
creatorAccess.planForCreate.mockImplementation(async (_w, _u, p: string) =>
|
|
614
614
|
p === 'knowledge-base/KnowledgeBase/Mine/doc.md'
|
|
@@ -622,10 +622,10 @@ describe('LockingFilesystem — creator read grants on creation', () => {
|
|
|
622
622
|
);
|
|
623
623
|
const fsLayer = new LockingFilesystem(
|
|
624
624
|
{ basePath: root, contained: true },
|
|
625
|
-
{
|
|
625
|
+
{ versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB, creatorAccess },
|
|
626
626
|
);
|
|
627
627
|
await fsLayer.writeFile('knowledge-base/KnowledgeBase/Mine/doc.md', 'body');
|
|
628
|
-
const locked = (
|
|
628
|
+
const locked = (versionControl.acquireLock as ReturnType<typeof vi.fn>).mock.calls.map(
|
|
629
629
|
(c) => c[2],
|
|
630
630
|
);
|
|
631
631
|
expect(locked).toEqual(['knowledge-base/KnowledgeBase/Mine/access.md', 'knowledge-base/KnowledgeBase/Mine/doc.md']);
|
|
@@ -638,7 +638,7 @@ describe('LockingFilesystem — creator read grants on creation', () => {
|
|
|
638
638
|
});
|
|
639
639
|
|
|
640
640
|
it('mkdir seeds the new folder access.md and then skips the .gitkeep (dir not empty)', async () => {
|
|
641
|
-
const
|
|
641
|
+
const versionControl = makeVersionControl();
|
|
642
642
|
const creatorAccess = makeCreatorAccess();
|
|
643
643
|
creatorAccess.planForCreate.mockImplementation(async (_w, _u, p: string, kind: string) =>
|
|
644
644
|
kind === 'dir'
|
|
@@ -651,7 +651,7 @@ describe('LockingFilesystem — creator read grants on creation', () => {
|
|
|
651
651
|
);
|
|
652
652
|
const fsLayer = new LockingFilesystem(
|
|
653
653
|
{ basePath: root, contained: true },
|
|
654
|
-
{
|
|
654
|
+
{ versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB, creatorAccess },
|
|
655
655
|
);
|
|
656
656
|
await fsLayer.mkdir('knowledge-base/KnowledgeBase/Projects');
|
|
657
657
|
expect(
|
|
@@ -663,7 +663,7 @@ describe('LockingFilesystem — creator read grants on creation', () => {
|
|
|
663
663
|
});
|
|
664
664
|
|
|
665
665
|
it('writeFiles folds seeds into the same atomic batch, deduped across files', async () => {
|
|
666
|
-
const
|
|
666
|
+
const versionControl = makeVersionControl();
|
|
667
667
|
const creatorAccess = makeCreatorAccess();
|
|
668
668
|
creatorAccess.planForCreate.mockResolvedValue({
|
|
669
669
|
kind: 'seed-access-md',
|
|
@@ -672,7 +672,7 @@ describe('LockingFilesystem — creator read grants on creation', () => {
|
|
|
672
672
|
});
|
|
673
673
|
const fsLayer = new LockingFilesystem(
|
|
674
674
|
{ basePath: root, contained: true },
|
|
675
|
-
{
|
|
675
|
+
{ versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB, creatorAccess },
|
|
676
676
|
);
|
|
677
677
|
await fsLayer.writeFiles(
|
|
678
678
|
[
|
|
@@ -685,8 +685,8 @@ describe('LockingFilesystem — creator read grants on creation', () => {
|
|
|
685
685
|
expect(
|
|
686
686
|
await fs.readFile(path.join(root, 'knowledge-base/KnowledgeBase/Mine/access.md'), 'utf-8'),
|
|
687
687
|
).toContain('Alice <alice@example.com>');
|
|
688
|
-
expect(
|
|
689
|
-
const locked = (
|
|
688
|
+
expect(versionControl.commitChanges).toHaveBeenCalledTimes(1);
|
|
689
|
+
const locked = (versionControl.acquireLock as ReturnType<typeof vi.fn>).mock.calls.map(
|
|
690
690
|
(c) => c[2],
|
|
691
691
|
);
|
|
692
692
|
expect(locked).toEqual([
|
|
@@ -707,7 +707,7 @@ describe('LockingFilesystem — creator read grants on creation', () => {
|
|
|
707
707
|
await fs.mkdir(path.join(root, 'knowledge-base/KnowledgeBase/Mine'), { recursive: true });
|
|
708
708
|
const seedContent = '---\nread:\n - Alice <alice@example.com>\n---\n';
|
|
709
709
|
await fs.writeFile(path.join(root, 'knowledge-base/KnowledgeBase/Mine/access.md'), seedContent);
|
|
710
|
-
const
|
|
710
|
+
const versionControl = makeVersionControl();
|
|
711
711
|
const creatorAccess = makeCreatorAccess();
|
|
712
712
|
creatorAccess.planForCreate.mockResolvedValue({
|
|
713
713
|
kind: 'seed-access-md',
|
|
@@ -718,7 +718,7 @@ describe('LockingFilesystem — creator read grants on creation', () => {
|
|
|
718
718
|
const fsLayer = new LockingFilesystem(
|
|
719
719
|
{ basePath: root, contained: true },
|
|
720
720
|
{
|
|
721
|
-
|
|
721
|
+
versionControl,
|
|
722
722
|
workspaceId: 'ws-feat',
|
|
723
723
|
branch: 'feat',
|
|
724
724
|
user: USER, kbDirName: KB,
|
|
@@ -729,10 +729,10 @@ describe('LockingFilesystem — creator read grants on creation', () => {
|
|
|
729
729
|
await fsLayer.writeFiles([{ path: 'knowledge-base/KnowledgeBase/Mine/doc.md', content: 'body' }], 'batch');
|
|
730
730
|
|
|
731
731
|
// The seed's lock cycle still ran (acquired + released)...
|
|
732
|
-
const locked = (
|
|
732
|
+
const locked = (versionControl.acquireLock as ReturnType<typeof vi.fn>).mock.calls.map((c) => c[2]);
|
|
733
733
|
expect(locked).toContain('knowledge-base/KnowledgeBase/Mine/access.md');
|
|
734
734
|
// ...but the commit is scoped to the caller's path only.
|
|
735
|
-
const commitPaths = (
|
|
735
|
+
const commitPaths = (versionControl.commitChanges as ReturnType<typeof vi.fn>).mock.calls[0][3];
|
|
736
736
|
expect(commitPaths).toEqual(['knowledge-base/KnowledgeBase/Mine/doc.md']);
|
|
737
737
|
expect(emitted[0].paths).toEqual(['knowledge-base/KnowledgeBase/Mine/doc.md']);
|
|
738
738
|
// The seeded file is untouched.
|
|
@@ -756,7 +756,7 @@ describe('LockingFilesystem — creator read grants on creation', () => {
|
|
|
756
756
|
await fs.mkdir(path.join(root, 'knowledge-base/KnowledgeBase/Mine'), { recursive: true });
|
|
757
757
|
const priorSave = '---\nread:\n - Alice <alice@example.com>\n---\nprior queued bytes\n';
|
|
758
758
|
await fs.writeFile(path.join(root, 'knowledge-base/KnowledgeBase/Mine/access.md'), priorSave);
|
|
759
|
-
const
|
|
759
|
+
const versionControl = makeVersionControl();
|
|
760
760
|
const creatorAccess = makeCreatorAccess();
|
|
761
761
|
creatorAccess.planForCreate.mockResolvedValue({
|
|
762
762
|
kind: 'seed-access-md',
|
|
@@ -765,21 +765,21 @@ describe('LockingFilesystem — creator read grants on creation', () => {
|
|
|
765
765
|
});
|
|
766
766
|
const fsLayer = new LockingFilesystem(
|
|
767
767
|
{ basePath: root, contained: true },
|
|
768
|
-
{
|
|
768
|
+
{ versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB, creatorAccess },
|
|
769
769
|
);
|
|
770
770
|
await fsLayer.writeFiles([{ path: 'knowledge-base/KnowledgeBase/Mine/doc.md', content: 'body' }], 'batch');
|
|
771
771
|
|
|
772
772
|
// The batch's OWN committed path releases no-commit (clean — discard no-ops)...
|
|
773
|
-
expect(
|
|
773
|
+
expect(versionControl.releaseLockNoCommit).toHaveBeenCalledWith(
|
|
774
774
|
'ws-feat', 'feat', 'knowledge-base/KnowledgeBase/Mine/doc.md', USER,
|
|
775
775
|
);
|
|
776
776
|
// ...but the merely-locked seed path releases UNTOUCHED — never the
|
|
777
777
|
// enqueue, never the discard.
|
|
778
|
-
expect(
|
|
778
|
+
expect(versionControl.releaseLockUntouched).toHaveBeenCalledWith(
|
|
779
779
|
'ws-feat', 'feat', 'knowledge-base/KnowledgeBase/Mine/access.md', USER,
|
|
780
780
|
);
|
|
781
|
-
expect(
|
|
782
|
-
expect(
|
|
781
|
+
expect(versionControl.releaseLock).not.toHaveBeenCalled();
|
|
782
|
+
expect(versionControl.releaseLockNoCommit).not.toHaveBeenCalledWith(
|
|
783
783
|
'ws-feat', 'feat', 'knowledge-base/KnowledgeBase/Mine/access.md', USER,
|
|
784
784
|
);
|
|
785
785
|
});
|
|
@@ -788,13 +788,13 @@ describe('LockingFilesystem — creator read grants on creation', () => {
|
|
|
788
788
|
// First path acquires, second is contended: nothing has been written, so
|
|
789
789
|
// the first path may hold ONLY someone else's still-queued bytes — the
|
|
790
790
|
// unwind must neither discard them nor enqueue them under this user.
|
|
791
|
-
const
|
|
792
|
-
(
|
|
791
|
+
const versionControl = makeVersionControl();
|
|
792
|
+
(versionControl.acquireLock as ReturnType<typeof vi.fn>)
|
|
793
793
|
.mockResolvedValueOnce({ acquired: true, lock: { holderUserId: 'user-1', holderName: 'Alice' } })
|
|
794
794
|
.mockResolvedValue({ acquired: false, lock: { holderUserId: 'bob', holderName: 'Bob' } });
|
|
795
795
|
const fsLayer = new LockingFilesystem(
|
|
796
796
|
{ basePath: root, contained: true },
|
|
797
|
-
{
|
|
797
|
+
{ versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB },
|
|
798
798
|
);
|
|
799
799
|
await expect(
|
|
800
800
|
fsLayer.writeFiles(
|
|
@@ -805,9 +805,9 @@ describe('LockingFilesystem — creator read grants on creation', () => {
|
|
|
805
805
|
'batch',
|
|
806
806
|
),
|
|
807
807
|
).rejects.toThrow(/locked by Bob/);
|
|
808
|
-
expect(
|
|
809
|
-
expect(
|
|
810
|
-
expect(
|
|
808
|
+
expect(versionControl.releaseLockUntouched).toHaveBeenCalledWith('ws-feat', 'feat', 'knowledge-base/A.md', USER);
|
|
809
|
+
expect(versionControl.releaseLock).not.toHaveBeenCalled();
|
|
810
|
+
expect(versionControl.releaseLockNoCommit).not.toHaveBeenCalled();
|
|
811
811
|
}, 10_000);
|
|
812
812
|
|
|
813
813
|
it('a DELETE batch whose later lock is contended deletes nothing — a folder never half-disappears', async () => {
|
|
@@ -817,13 +817,13 @@ describe('LockingFilesystem — creator read grants on creation', () => {
|
|
|
817
817
|
await fs.mkdir(path.join(root, 'knowledge-base/Archive'), { recursive: true });
|
|
818
818
|
await fs.writeFile(path.join(root, 'knowledge-base/Archive/a.md'), 'a');
|
|
819
819
|
await fs.writeFile(path.join(root, 'knowledge-base/Archive/b.md'), 'b');
|
|
820
|
-
const
|
|
821
|
-
(
|
|
820
|
+
const versionControl = makeVersionControl();
|
|
821
|
+
(versionControl.acquireLock as ReturnType<typeof vi.fn>)
|
|
822
822
|
.mockResolvedValueOnce({ acquired: true, lock: { holderUserId: 'user-1', holderName: 'Alice' } })
|
|
823
823
|
.mockResolvedValue({ acquired: false, lock: { holderUserId: 'bob', holderName: 'Bob' } });
|
|
824
824
|
const fsLayer = new LockingFilesystem(
|
|
825
825
|
{ basePath: root, contained: true },
|
|
826
|
-
{
|
|
826
|
+
{ versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB },
|
|
827
827
|
);
|
|
828
828
|
|
|
829
829
|
await expect(
|
|
@@ -832,7 +832,7 @@ describe('LockingFilesystem — creator read grants on creation', () => {
|
|
|
832
832
|
|
|
833
833
|
expect(await fs.readFile(path.join(root, 'knowledge-base/Archive/a.md'), 'utf-8')).toBe('a');
|
|
834
834
|
expect(await fs.readFile(path.join(root, 'knowledge-base/Archive/b.md'), 'utf-8')).toBe('b');
|
|
835
|
-
expect(
|
|
835
|
+
expect(versionControl.commitChanges).not.toHaveBeenCalled();
|
|
836
836
|
}, 10_000);
|
|
837
837
|
|
|
838
838
|
it('a seed write that dies MID-WRITE restores the pre-image and releases UNTOUCHED', async () => {
|
|
@@ -845,7 +845,7 @@ describe('LockingFilesystem — creator read grants on creation', () => {
|
|
|
845
845
|
const priorBytes = 'prior queued bytes\n';
|
|
846
846
|
const seedPath = 'knowledge-base/KnowledgeBase/Mine/access.md';
|
|
847
847
|
await fs.writeFile(path.join(root, seedPath), priorBytes);
|
|
848
|
-
const
|
|
848
|
+
const versionControl = makeVersionControl();
|
|
849
849
|
const creatorAccess = makeCreatorAccess();
|
|
850
850
|
creatorAccess.planForCreate.mockResolvedValue({
|
|
851
851
|
kind: 'seed-access-md',
|
|
@@ -854,7 +854,7 @@ describe('LockingFilesystem — creator read grants on creation', () => {
|
|
|
854
854
|
});
|
|
855
855
|
const fsLayer = new LockingFilesystem(
|
|
856
856
|
{ basePath: root, contained: true },
|
|
857
|
-
{
|
|
857
|
+
{ versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB, creatorAccess },
|
|
858
858
|
);
|
|
859
859
|
// Fail the SEED write once (simulating a mid-write death that left
|
|
860
860
|
// partial bytes); every other write — including the restore — passes
|
|
@@ -879,13 +879,13 @@ describe('LockingFilesystem — creator read grants on creation', () => {
|
|
|
879
879
|
// Pre-image restored — the partial bytes did not outlive the batch.
|
|
880
880
|
expect(await fs.readFile(path.join(root, seedPath), 'utf-8')).toBe(priorBytes);
|
|
881
881
|
// The failed seed stays OUT of the commit scope and releases untouched.
|
|
882
|
-
const commitPaths = (
|
|
882
|
+
const commitPaths = (versionControl.commitChanges as ReturnType<typeof vi.fn>).mock.calls[0][3];
|
|
883
883
|
expect(commitPaths).toEqual(['knowledge-base/KnowledgeBase/Mine/doc.md']);
|
|
884
|
-
expect(
|
|
885
|
-
expect(
|
|
884
|
+
expect(versionControl.releaseLockUntouched).toHaveBeenCalledWith('ws-feat', 'feat', seedPath, USER);
|
|
885
|
+
expect(versionControl.releaseLockNoCommit).not.toHaveBeenCalledWith(
|
|
886
886
|
'ws-feat', 'feat', seedPath, USER,
|
|
887
887
|
);
|
|
888
|
-
expect(
|
|
888
|
+
expect(versionControl.releaseLock).not.toHaveBeenCalled();
|
|
889
889
|
});
|
|
890
890
|
|
|
891
891
|
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 +899,8 @@ describe('LockingFilesystem — creator read grants on creation', () => {
|
|
|
899
899
|
const priorBytes = 'prior queued bytes\n';
|
|
900
900
|
const seedPath = 'knowledge-base/KnowledgeBase/Mine/access.md';
|
|
901
901
|
await fs.writeFile(path.join(root, seedPath), priorBytes);
|
|
902
|
-
const
|
|
903
|
-
(
|
|
902
|
+
const versionControl = makeVersionControl();
|
|
903
|
+
(versionControl.commitChanges as ReturnType<typeof vi.fn>).mockRejectedValue(
|
|
904
904
|
new Error('commit exploded'),
|
|
905
905
|
);
|
|
906
906
|
const creatorAccess = makeCreatorAccess();
|
|
@@ -911,7 +911,7 @@ describe('LockingFilesystem — creator read grants on creation', () => {
|
|
|
911
911
|
});
|
|
912
912
|
const fsLayer = new LockingFilesystem(
|
|
913
913
|
{ basePath: root, contained: true },
|
|
914
|
-
{
|
|
914
|
+
{ versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB, creatorAccess },
|
|
915
915
|
);
|
|
916
916
|
await expect(
|
|
917
917
|
fsLayer.writeFiles([{ path: 'knowledge-base/KnowledgeBase/Mine/doc.md', content: 'body' }], 'batch'),
|
|
@@ -920,11 +920,11 @@ describe('LockingFilesystem — creator read grants on creation', () => {
|
|
|
920
920
|
expect(await fs.readFile(path.join(root, seedPath), 'utf-8')).toBe(priorBytes);
|
|
921
921
|
// …and the seed releases UNTOUCHED (the prior queued bytes are not this
|
|
922
922
|
// batch's to discard), while the caller's own write releases via discard.
|
|
923
|
-
expect(
|
|
924
|
-
expect(
|
|
923
|
+
expect(versionControl.releaseLockUntouched).toHaveBeenCalledWith('ws-feat', 'feat', seedPath, USER);
|
|
924
|
+
expect(versionControl.releaseLockNoCommit).toHaveBeenCalledWith(
|
|
925
925
|
'ws-feat', 'feat', 'knowledge-base/KnowledgeBase/Mine/doc.md', USER,
|
|
926
926
|
);
|
|
927
|
-
expect(
|
|
927
|
+
expect(versionControl.releaseLockNoCommit).not.toHaveBeenCalledWith(
|
|
928
928
|
'ws-feat', 'feat', seedPath, USER,
|
|
929
929
|
);
|
|
930
930
|
});
|
|
@@ -937,7 +937,7 @@ describe('LockingFilesystem — creator read grants on creation', () => {
|
|
|
937
937
|
await fs.mkdir(path.join(root, 'knowledge-base/KnowledgeBase/Mine'), { recursive: true });
|
|
938
938
|
const seedPath = 'knowledge-base/KnowledgeBase/Mine/access.md';
|
|
939
939
|
await fs.writeFile(path.join(root, seedPath), 'prior\n');
|
|
940
|
-
const
|
|
940
|
+
const versionControl = makeVersionControl();
|
|
941
941
|
const creatorAccess = makeCreatorAccess();
|
|
942
942
|
creatorAccess.planForCreate.mockResolvedValue({
|
|
943
943
|
kind: 'seed-access-md',
|
|
@@ -946,7 +946,7 @@ describe('LockingFilesystem — creator read grants on creation', () => {
|
|
|
946
946
|
});
|
|
947
947
|
const fsLayer = new LockingFilesystem(
|
|
948
948
|
{ basePath: root, contained: true },
|
|
949
|
-
{
|
|
949
|
+
{ versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB, creatorAccess },
|
|
950
950
|
);
|
|
951
951
|
const original = LocalFilesystem.prototype.writeFile;
|
|
952
952
|
const spy = vi
|
|
@@ -962,12 +962,12 @@ describe('LockingFilesystem — creator read grants on creation', () => {
|
|
|
962
962
|
}
|
|
963
963
|
// The unrecoverable seed path releases via the discard, everything else
|
|
964
964
|
// untouched-or-no-commit as usual; the batch itself still succeeds.
|
|
965
|
-
expect(
|
|
966
|
-
expect(
|
|
965
|
+
expect(versionControl.releaseLockNoCommit).toHaveBeenCalledWith('ws-feat', 'feat', seedPath, USER);
|
|
966
|
+
expect(versionControl.releaseLockUntouched).not.toHaveBeenCalledWith(
|
|
967
967
|
'ws-feat', 'feat', seedPath, USER,
|
|
968
968
|
);
|
|
969
|
-
expect(
|
|
970
|
-
const commitPaths = (
|
|
969
|
+
expect(versionControl.releaseLock).not.toHaveBeenCalled();
|
|
970
|
+
const commitPaths = (versionControl.commitChanges as ReturnType<typeof vi.fn>).mock.calls[0][3];
|
|
971
971
|
expect(commitPaths).toEqual(['knowledge-base/KnowledgeBase/Mine/doc.md']);
|
|
972
972
|
});
|
|
973
973
|
|
|
@@ -980,8 +980,8 @@ describe('LockingFilesystem — creator read grants on creation', () => {
|
|
|
980
980
|
await fs.mkdir(path.join(root, 'knowledge-base/KnowledgeBase/Mine'), { recursive: true });
|
|
981
981
|
const seedPath = 'knowledge-base/KnowledgeBase/Mine/access.md';
|
|
982
982
|
await fs.writeFile(path.join(root, seedPath), 'grant already present\n');
|
|
983
|
-
const
|
|
984
|
-
(
|
|
983
|
+
const versionControl = makeVersionControl();
|
|
984
|
+
(versionControl.commitChanges as ReturnType<typeof vi.fn>).mockRejectedValue(
|
|
985
985
|
new PushNeedsAgentResolutionError('feat', '(batch)', 'non-fast-forward', 'rebase failed'),
|
|
986
986
|
);
|
|
987
987
|
const creatorAccess = makeCreatorAccess();
|
|
@@ -992,21 +992,21 @@ describe('LockingFilesystem — creator read grants on creation', () => {
|
|
|
992
992
|
});
|
|
993
993
|
const fsLayer = new LockingFilesystem(
|
|
994
994
|
{ basePath: root, contained: true },
|
|
995
|
-
{
|
|
995
|
+
{ versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB, creatorAccess },
|
|
996
996
|
);
|
|
997
997
|
await expect(
|
|
998
998
|
fsLayer.writeFiles([{ path: 'knowledge-base/KnowledgeBase/Mine/doc.md', content: 'body' }], 'batch'),
|
|
999
999
|
).rejects.toBeInstanceOf(PushNeedsAgentResolutionError);
|
|
1000
|
-
expect(
|
|
1000
|
+
expect(versionControl.releaseLock).toHaveBeenCalledWith(
|
|
1001
1001
|
'ws-feat', 'feat', 'knowledge-base/KnowledgeBase/Mine/doc.md', USER,
|
|
1002
1002
|
);
|
|
1003
|
-
expect(
|
|
1004
|
-
expect(
|
|
1005
|
-
expect(
|
|
1003
|
+
expect(versionControl.releaseLockUntouched).toHaveBeenCalledWith('ws-feat', 'feat', seedPath, USER);
|
|
1004
|
+
expect(versionControl.releaseLock).not.toHaveBeenCalledWith('ws-feat', 'feat', seedPath, USER);
|
|
1005
|
+
expect(versionControl.releaseLockNoCommit).not.toHaveBeenCalled();
|
|
1006
1006
|
});
|
|
1007
1007
|
|
|
1008
1008
|
it('a LANDED seed rides the commit scope with the caller paths', async () => {
|
|
1009
|
-
const
|
|
1009
|
+
const versionControl = makeVersionControl();
|
|
1010
1010
|
const creatorAccess = makeCreatorAccess();
|
|
1011
1011
|
creatorAccess.planForCreate.mockResolvedValue({
|
|
1012
1012
|
kind: 'seed-access-md',
|
|
@@ -1015,20 +1015,20 @@ describe('LockingFilesystem — creator read grants on creation', () => {
|
|
|
1015
1015
|
});
|
|
1016
1016
|
const fsLayer = new LockingFilesystem(
|
|
1017
1017
|
{ basePath: root, contained: true },
|
|
1018
|
-
{
|
|
1018
|
+
{ versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB, creatorAccess },
|
|
1019
1019
|
);
|
|
1020
1020
|
await fsLayer.writeFiles([{ path: 'knowledge-base/KnowledgeBase/Mine/doc.md', content: 'body' }], 'batch');
|
|
1021
|
-
const commitPaths = (
|
|
1021
|
+
const commitPaths = (versionControl.commitChanges as ReturnType<typeof vi.fn>).mock.calls[0][3];
|
|
1022
1022
|
expect(commitPaths).toEqual(['knowledge-base/KnowledgeBase/Mine/doc.md', 'knowledge-base/KnowledgeBase/Mine/access.md']);
|
|
1023
1023
|
});
|
|
1024
1024
|
|
|
1025
1025
|
it('a planner failure never blocks the write', async () => {
|
|
1026
|
-
const
|
|
1026
|
+
const versionControl = makeVersionControl();
|
|
1027
1027
|
const creatorAccess = makeCreatorAccess();
|
|
1028
1028
|
creatorAccess.planForCreate.mockRejectedValue(new Error('planner down'));
|
|
1029
1029
|
const fsLayer = new LockingFilesystem(
|
|
1030
1030
|
{ basePath: root, contained: true },
|
|
1031
|
-
{
|
|
1031
|
+
{ versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB, creatorAccess },
|
|
1032
1032
|
);
|
|
1033
1033
|
await fsLayer.writeFile('knowledge-base/KnowledgeBase/new.md', 'x');
|
|
1034
1034
|
expect(await fs.readFile(path.join(root, 'knowledge-base/KnowledgeBase/new.md'), 'utf-8')).toBe('x');
|
|
@@ -1064,50 +1064,50 @@ describe('LockingFilesystem refuses to create anything outside the repository fo
|
|
|
1064
1064
|
};
|
|
1065
1065
|
}
|
|
1066
1066
|
|
|
1067
|
-
function layer(
|
|
1067
|
+
function layer(versionControl: IVersionControlService) {
|
|
1068
1068
|
const creatorAccess = makeCreatorAccess();
|
|
1069
1069
|
const validateWrite = vi.fn();
|
|
1070
1070
|
const fsLayer = new LockingFilesystem(
|
|
1071
1071
|
{ basePath: root, contained: true },
|
|
1072
|
-
{
|
|
1072
|
+
{ versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB, creatorAccess, validateWrite },
|
|
1073
1073
|
);
|
|
1074
1074
|
return { fsLayer, creatorAccess, validateWrite };
|
|
1075
1075
|
}
|
|
1076
1076
|
|
|
1077
1077
|
async function expectRefused(
|
|
1078
1078
|
op: Promise<unknown>,
|
|
1079
|
-
|
|
1079
|
+
versionControl: IVersionControlService,
|
|
1080
1080
|
creatorAccess: ReturnType<typeof makeCreatorAccess>,
|
|
1081
1081
|
validateWrite: ReturnType<typeof vi.fn>,
|
|
1082
1082
|
): Promise<void> {
|
|
1083
|
-
await expect(op).rejects.toBeInstanceOf(
|
|
1083
|
+
await expect(op).rejects.toBeInstanceOf(VersionControlValidationError);
|
|
1084
1084
|
// The corrected path is spelled out, under the clone folder.
|
|
1085
1085
|
await expect(op).rejects.toThrow('"knowledge-base/KnowledgeBase/Reviews');
|
|
1086
1086
|
// Refused before any side effect.
|
|
1087
|
-
expect(
|
|
1088
|
-
expect(
|
|
1089
|
-
expect(
|
|
1090
|
-
expect(
|
|
1087
|
+
expect(versionControl.acquireLock).not.toHaveBeenCalled();
|
|
1088
|
+
expect(versionControl.releaseLock).not.toHaveBeenCalled();
|
|
1089
|
+
expect(versionControl.releaseLockNoCommit).not.toHaveBeenCalled();
|
|
1090
|
+
expect(versionControl.commitChanges).not.toHaveBeenCalled();
|
|
1091
1091
|
expect(creatorAccess.planForCreate).not.toHaveBeenCalled();
|
|
1092
1092
|
expect(validateWrite).not.toHaveBeenCalled();
|
|
1093
1093
|
await expect(fs.access(path.join(root, 'KnowledgeBase'))).rejects.toBeDefined();
|
|
1094
1094
|
}
|
|
1095
1095
|
|
|
1096
1096
|
it('writeFile', async () => {
|
|
1097
|
-
const
|
|
1098
|
-
const { fsLayer, creatorAccess, validateWrite } = layer(
|
|
1099
|
-
await expectRefused(fsLayer.writeFile(STRAY, '<p>review</p>'),
|
|
1097
|
+
const versionControl = makeVersionControl();
|
|
1098
|
+
const { fsLayer, creatorAccess, validateWrite } = layer(versionControl);
|
|
1099
|
+
await expectRefused(fsLayer.writeFile(STRAY, '<p>review</p>'), versionControl, creatorAccess, validateWrite);
|
|
1100
1100
|
});
|
|
1101
1101
|
|
|
1102
1102
|
it('appendFile', async () => {
|
|
1103
|
-
const
|
|
1104
|
-
const { fsLayer, creatorAccess, validateWrite } = layer(
|
|
1105
|
-
await expectRefused(fsLayer.appendFile('KnowledgeBase/Reviews/review.log', 'line\n'),
|
|
1103
|
+
const versionControl = makeVersionControl();
|
|
1104
|
+
const { fsLayer, creatorAccess, validateWrite } = layer(versionControl);
|
|
1105
|
+
await expectRefused(fsLayer.appendFile('KnowledgeBase/Reviews/review.log', 'line\n'), versionControl, creatorAccess, validateWrite);
|
|
1106
1106
|
});
|
|
1107
1107
|
|
|
1108
1108
|
it('writeFiles refuses the whole batch when one path is outside: nothing written, nothing locked', async () => {
|
|
1109
|
-
const
|
|
1110
|
-
const { fsLayer, creatorAccess, validateWrite } = layer(
|
|
1109
|
+
const versionControl = makeVersionControl();
|
|
1110
|
+
const { fsLayer, creatorAccess, validateWrite } = layer(versionControl);
|
|
1111
1111
|
await expectRefused(
|
|
1112
1112
|
fsLayer.writeFiles(
|
|
1113
1113
|
[
|
|
@@ -1116,7 +1116,7 @@ describe('LockingFilesystem refuses to create anything outside the repository fo
|
|
|
1116
1116
|
],
|
|
1117
1117
|
'batch',
|
|
1118
1118
|
),
|
|
1119
|
-
|
|
1119
|
+
versionControl,
|
|
1120
1120
|
creatorAccess,
|
|
1121
1121
|
validateWrite,
|
|
1122
1122
|
);
|
|
@@ -1124,19 +1124,19 @@ describe('LockingFilesystem refuses to create anything outside the repository fo
|
|
|
1124
1124
|
});
|
|
1125
1125
|
|
|
1126
1126
|
it('mkdir', async () => {
|
|
1127
|
-
const
|
|
1128
|
-
const { fsLayer, creatorAccess, validateWrite } = layer(
|
|
1129
|
-
await expectRefused(fsLayer.mkdir('KnowledgeBase/Reviews'),
|
|
1127
|
+
const versionControl = makeVersionControl();
|
|
1128
|
+
const { fsLayer, creatorAccess, validateWrite } = layer(versionControl);
|
|
1129
|
+
await expectRefused(fsLayer.mkdir('KnowledgeBase/Reviews'), versionControl, creatorAccess, validateWrite);
|
|
1130
1130
|
});
|
|
1131
1131
|
|
|
1132
1132
|
it('moveFile refuses a destination outside the repository and leaves the source in place', async () => {
|
|
1133
1133
|
await fs.mkdir(path.join(root, 'knowledge-base/KnowledgeBase'), { recursive: true });
|
|
1134
1134
|
await fs.writeFile(path.join(root, 'knowledge-base/KnowledgeBase/PR-12.html'), 'x');
|
|
1135
|
-
const
|
|
1136
|
-
const { fsLayer, creatorAccess, validateWrite } = layer(
|
|
1135
|
+
const versionControl = makeVersionControl();
|
|
1136
|
+
const { fsLayer, creatorAccess, validateWrite } = layer(versionControl);
|
|
1137
1137
|
await expectRefused(
|
|
1138
1138
|
fsLayer.moveFile('knowledge-base/KnowledgeBase/PR-12.html', STRAY),
|
|
1139
|
-
|
|
1139
|
+
versionControl,
|
|
1140
1140
|
creatorAccess,
|
|
1141
1141
|
validateWrite,
|
|
1142
1142
|
);
|
|
@@ -1146,11 +1146,11 @@ describe('LockingFilesystem refuses to create anything outside the repository fo
|
|
|
1146
1146
|
it('copyFile refuses a destination outside the repository', async () => {
|
|
1147
1147
|
await fs.mkdir(path.join(root, 'knowledge-base/KnowledgeBase'), { recursive: true });
|
|
1148
1148
|
await fs.writeFile(path.join(root, 'knowledge-base/KnowledgeBase/PR-12.html'), 'x');
|
|
1149
|
-
const
|
|
1150
|
-
const { fsLayer, creatorAccess, validateWrite } = layer(
|
|
1149
|
+
const versionControl = makeVersionControl();
|
|
1150
|
+
const { fsLayer, creatorAccess, validateWrite } = layer(versionControl);
|
|
1151
1151
|
await expectRefused(
|
|
1152
1152
|
fsLayer.copyFile('knowledge-base/KnowledgeBase/PR-12.html', STRAY),
|
|
1153
|
-
|
|
1153
|
+
versionControl,
|
|
1154
1154
|
creatorAccess,
|
|
1155
1155
|
validateWrite,
|
|
1156
1156
|
);
|
|
@@ -1160,20 +1160,20 @@ describe('LockingFilesystem refuses to create anything outside the repository fo
|
|
|
1160
1160
|
// Containment is checked against the WORKSPACE dir, so the underlying
|
|
1161
1161
|
// filesystem would happily resolve `knowledge-base/../stray.md` to a file
|
|
1162
1162
|
// 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(
|
|
1163
|
+
const versionControl = makeVersionControl();
|
|
1164
|
+
const { fsLayer, creatorAccess, validateWrite } = layer(versionControl);
|
|
1165
|
+
await expect(fsLayer.writeFile('knowledge-base/../stray.md', 'x')).rejects.toBeInstanceOf(VersionControlValidationError);
|
|
1166
|
+
expect(versionControl.acquireLock).not.toHaveBeenCalled();
|
|
1167
1167
|
expect(creatorAccess.planForCreate).not.toHaveBeenCalled();
|
|
1168
1168
|
expect(validateWrite).not.toHaveBeenCalled();
|
|
1169
1169
|
await expect(fs.access(path.join(root, 'stray.md'))).rejects.toBeDefined();
|
|
1170
1170
|
});
|
|
1171
1171
|
|
|
1172
1172
|
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(
|
|
1173
|
+
const versionControl = makeVersionControl();
|
|
1174
|
+
const { fsLayer } = layer(versionControl);
|
|
1175
|
+
await expect(fsLayer.writeFile('knowledge-based/x.md', 'x')).rejects.toBeInstanceOf(VersionControlValidationError);
|
|
1176
|
+
expect(versionControl.acquireLock).not.toHaveBeenCalled();
|
|
1177
1177
|
});
|
|
1178
1178
|
|
|
1179
1179
|
it('deleteFile still removes a file the old behaviour left beside the repository', async () => {
|
|
@@ -1181,8 +1181,8 @@ describe('LockingFilesystem refuses to create anything outside the repository fo
|
|
|
1181
1181
|
// created before this guard existed must be able to clean it up.
|
|
1182
1182
|
await fs.mkdir(path.join(root, 'KnowledgeBase/Reviews'), { recursive: true });
|
|
1183
1183
|
await fs.writeFile(path.join(root, STRAY), 'stray');
|
|
1184
|
-
const
|
|
1185
|
-
const { fsLayer } = layer(
|
|
1184
|
+
const versionControl = makeVersionControl();
|
|
1185
|
+
const { fsLayer } = layer(versionControl);
|
|
1186
1186
|
await fsLayer.deleteFile(STRAY);
|
|
1187
1187
|
await expect(fs.access(path.join(root, STRAY))).rejects.toBeDefined();
|
|
1188
1188
|
});
|
|
@@ -1191,8 +1191,8 @@ describe('LockingFilesystem refuses to create anything outside the repository fo
|
|
|
1191
1191
|
await fs.mkdir(path.join(root, 'KnowledgeBase/Reviews'), { recursive: true });
|
|
1192
1192
|
await fs.writeFile(path.join(root, STRAY), 'stray');
|
|
1193
1193
|
await fs.mkdir(path.join(root, 'knowledge-base'), { recursive: true });
|
|
1194
|
-
const
|
|
1195
|
-
const { fsLayer } = layer(
|
|
1194
|
+
const versionControl = makeVersionControl();
|
|
1195
|
+
const { fsLayer } = layer(versionControl);
|
|
1196
1196
|
await fsLayer.moveFile(STRAY, CORRECTED);
|
|
1197
1197
|
expect(await fs.readFile(path.join(root, CORRECTED), 'utf-8')).toBe('stray');
|
|
1198
1198
|
await expect(fs.access(path.join(root, STRAY))).rejects.toBeDefined();
|
|
@@ -1217,85 +1217,85 @@ describe('LockingFilesystem — the agent roles.yaml gate covers every op that l
|
|
|
1217
1217
|
await fs.rm(root, { recursive: true, force: true });
|
|
1218
1218
|
});
|
|
1219
1219
|
|
|
1220
|
-
function layer(
|
|
1220
|
+
function layer(versionControl: IVersionControlService) {
|
|
1221
1221
|
const validateWrite = makeAgentRolesYamlWriteValidator(KB, () =>
|
|
1222
1222
|
fs.readFile(path.join(root, ROLES), 'utf-8').catch(() => null),
|
|
1223
1223
|
);
|
|
1224
1224
|
return new LockingFilesystem(
|
|
1225
1225
|
{ basePath: root, contained: true },
|
|
1226
|
-
{
|
|
1226
|
+
{ versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB, validateWrite },
|
|
1227
1227
|
);
|
|
1228
1228
|
}
|
|
1229
1229
|
|
|
1230
|
-
async function expectRefused(op: Promise<unknown>,
|
|
1230
|
+
async function expectRefused(op: Promise<unknown>, versionControl: IVersionControlService): Promise<void> {
|
|
1231
1231
|
await expect(op).rejects.toBeInstanceOf(RolesYamlNewRoleError);
|
|
1232
|
-
expect(
|
|
1232
|
+
expect(versionControl.acquireLock).not.toHaveBeenCalled();
|
|
1233
1233
|
expect(await fs.readFile(path.join(root, ROLES), 'utf-8')).toBe(CURRENT);
|
|
1234
1234
|
}
|
|
1235
1235
|
|
|
1236
1236
|
it('writeFile', async () => {
|
|
1237
|
-
const
|
|
1238
|
-
await expectRefused(layer(
|
|
1237
|
+
const versionControl = makeVersionControl();
|
|
1238
|
+
await expectRefused(layer(versionControl).writeFile(ROLES, CURRENT + NEW_ROLE), versionControl);
|
|
1239
1239
|
});
|
|
1240
1240
|
|
|
1241
1241
|
it('writeFiles refuses the whole batch', async () => {
|
|
1242
|
-
const
|
|
1242
|
+
const versionControl = makeVersionControl();
|
|
1243
1243
|
await expectRefused(
|
|
1244
|
-
layer(
|
|
1245
|
-
|
|
1244
|
+
layer(versionControl).writeFiles([{ path: `${KB}/KnowledgeBase/ok.md`, content: 'fine' }, { path: ROLES, content: CURRENT + NEW_ROLE }], 'batch'),
|
|
1245
|
+
versionControl,
|
|
1246
1246
|
);
|
|
1247
1247
|
await expect(fs.access(path.join(root, KB, 'KnowledgeBase/ok.md'))).rejects.toBeDefined();
|
|
1248
1248
|
});
|
|
1249
1249
|
|
|
1250
1250
|
it('appendFile, judged by the file it would leave', async () => {
|
|
1251
|
-
const
|
|
1252
|
-
await expectRefused(layer(
|
|
1251
|
+
const versionControl = makeVersionControl();
|
|
1252
|
+
await expectRefused(layer(versionControl).appendFile(ROLES, NEW_ROLE), versionControl);
|
|
1253
1253
|
});
|
|
1254
1254
|
|
|
1255
1255
|
it('copyFile, judged by the source bytes', async () => {
|
|
1256
|
-
const
|
|
1257
|
-
await expectRefused(layer(
|
|
1256
|
+
const versionControl = makeVersionControl();
|
|
1257
|
+
await expectRefused(layer(versionControl).copyFile(DRAFT, ROLES, { overwrite: true }), versionControl);
|
|
1258
1258
|
});
|
|
1259
1259
|
|
|
1260
1260
|
it('moveFile, leaving the source where it was', async () => {
|
|
1261
|
-
const
|
|
1262
|
-
await expectRefused(layer(
|
|
1261
|
+
const versionControl = makeVersionControl();
|
|
1262
|
+
await expectRefused(layer(versionControl).moveFile(DRAFT, ROLES, { overwrite: true }), versionControl);
|
|
1263
1263
|
expect(await fs.readFile(path.join(root, DRAFT), 'utf-8')).toBe(CURRENT + NEW_ROLE);
|
|
1264
1264
|
});
|
|
1265
1265
|
|
|
1266
1266
|
it('a members-only write and append land as before', async () => {
|
|
1267
|
-
const
|
|
1268
|
-
const fsLayer = layer(
|
|
1267
|
+
const versionControl = makeVersionControl();
|
|
1268
|
+
const fsLayer = layer(versionControl);
|
|
1269
1269
|
await fsLayer.writeFile(ROLES, `${CURRENT} - b@x.eu\n`);
|
|
1270
1270
|
await fsLayer.appendFile(ROLES, ' - c@x.eu\n');
|
|
1271
1271
|
expect(await fs.readFile(path.join(root, ROLES), 'utf-8')).toBe(`${CURRENT} - b@x.eu\n - c@x.eu\n`);
|
|
1272
|
-
expect(
|
|
1272
|
+
expect(versionControl.releaseLock).toHaveBeenCalledTimes(2);
|
|
1273
1273
|
});
|
|
1274
1274
|
|
|
1275
1275
|
describe('judged again under the lock, against what a concurrent writer left there', () => {
|
|
1276
1276
|
const WITH_PHOENIX = CURRENT + NEW_ROLE;
|
|
1277
1277
|
|
|
1278
|
-
/** A
|
|
1279
|
-
function
|
|
1280
|
-
const
|
|
1281
|
-
const acquired = (
|
|
1278
|
+
/** A version-control service whose acquire lets `race` rewrite disk first — the lock holder that finished just before us. */
|
|
1279
|
+
function racingVersionControl(race: () => Promise<void>): IVersionControlService {
|
|
1280
|
+
const versionControl = makeVersionControl();
|
|
1281
|
+
const acquired = (versionControl.acquireLock as ReturnType<typeof vi.fn>).getMockImplementation()!;
|
|
1282
1282
|
let raced = false;
|
|
1283
|
-
(
|
|
1283
|
+
(versionControl.acquireLock as ReturnType<typeof vi.fn>).mockImplementation(async (...args: unknown[]) => {
|
|
1284
1284
|
if (!raced) {
|
|
1285
1285
|
raced = true;
|
|
1286
1286
|
await race();
|
|
1287
1287
|
}
|
|
1288
1288
|
return acquired(...args);
|
|
1289
1289
|
});
|
|
1290
|
-
return
|
|
1290
|
+
return versionControl;
|
|
1291
1291
|
}
|
|
1292
1292
|
|
|
1293
|
-
async function expectRefusedUnderLock(op: Promise<unknown>,
|
|
1293
|
+
async function expectRefusedUnderLock(op: Promise<unknown>, versionControl: IVersionControlService, locks: number): Promise<void> {
|
|
1294
1294
|
await expect(op).rejects.toBeInstanceOf(RolesYamlNewRoleError);
|
|
1295
|
-
expect(
|
|
1296
|
-
expect(
|
|
1297
|
-
expect(
|
|
1298
|
-
expect(
|
|
1295
|
+
expect(versionControl.releaseLockUntouched).toHaveBeenCalledTimes(locks);
|
|
1296
|
+
expect(versionControl.releaseLockNoCommit).not.toHaveBeenCalled();
|
|
1297
|
+
expect(versionControl.releaseLock).not.toHaveBeenCalled();
|
|
1298
|
+
expect(versionControl.commitChanges).not.toHaveBeenCalled();
|
|
1299
1299
|
expect(await fs.readFile(path.join(root, ROLES), 'utf-8')).toBe(CURRENT);
|
|
1300
1300
|
}
|
|
1301
1301
|
|
|
@@ -1306,15 +1306,15 @@ describe('LockingFilesystem — the agent roles.yaml gate covers every op that l
|
|
|
1306
1306
|
});
|
|
1307
1307
|
|
|
1308
1308
|
it('writeFile cannot reinstate a role deleted while it waited for the lock', async () => {
|
|
1309
|
-
const
|
|
1310
|
-
await expectRefusedUnderLock(layer(
|
|
1309
|
+
const versionControl = racingVersionControl(deletePhoenix);
|
|
1310
|
+
await expectRefusedUnderLock(layer(versionControl).writeFile(ROLES, `${WITH_PHOENIX} - q@x.eu\n`), versionControl, 1);
|
|
1311
1311
|
});
|
|
1312
1312
|
|
|
1313
1313
|
it('writeFiles re-judges the whole batch once every lock is held', async () => {
|
|
1314
|
-
const
|
|
1314
|
+
const versionControl = racingVersionControl(deletePhoenix);
|
|
1315
1315
|
await expectRefusedUnderLock(
|
|
1316
|
-
layer(
|
|
1317
|
-
|
|
1316
|
+
layer(versionControl).writeFiles([{ path: `${KB}/KnowledgeBase/ok.md`, content: 'fine' }, { path: ROLES, content: WITH_PHOENIX }], 'batch'),
|
|
1317
|
+
versionControl,
|
|
1318
1318
|
2,
|
|
1319
1319
|
);
|
|
1320
1320
|
await expect(fs.access(path.join(root, KB, 'KnowledgeBase/ok.md'))).rejects.toBeDefined();
|
|
@@ -1324,31 +1324,31 @@ describe('LockingFilesystem — the agent roles.yaml gate covers every op that l
|
|
|
1324
1324
|
// Appending never adds a role the file had before, so what a race can
|
|
1325
1325
|
// change is validity: the same line appended to the rewritten file breaks it.
|
|
1326
1326
|
const rewritten = 'roles:\n Admin:\n - a@x.eu';
|
|
1327
|
-
const
|
|
1328
|
-
await expect(layer(
|
|
1329
|
-
expect(
|
|
1330
|
-
expect(
|
|
1327
|
+
const versionControl = racingVersionControl(() => fs.writeFile(path.join(root, ROLES), rewritten));
|
|
1328
|
+
await expect(layer(versionControl).appendFile(ROLES, ' - b@x.eu\n')).rejects.toBeInstanceOf(RolesYamlInvalidError);
|
|
1329
|
+
expect(versionControl.releaseLockUntouched).toHaveBeenCalledTimes(1);
|
|
1330
|
+
expect(versionControl.releaseLockNoCommit).not.toHaveBeenCalled();
|
|
1331
1331
|
expect(await fs.readFile(path.join(root, ROLES), 'utf-8')).toBe(rewritten);
|
|
1332
1332
|
});
|
|
1333
1333
|
|
|
1334
1334
|
it('copyFile judges — and writes — the source bytes read under the lock', async () => {
|
|
1335
1335
|
await fs.writeFile(path.join(root, DRAFT), WITH_PHOENIX);
|
|
1336
|
-
const
|
|
1337
|
-
await expectRefusedUnderLock(layer(
|
|
1336
|
+
const versionControl = racingVersionControl(deletePhoenix);
|
|
1337
|
+
await expectRefusedUnderLock(layer(versionControl).copyFile(DRAFT, ROLES, { overwrite: true }), versionControl, 1);
|
|
1338
1338
|
});
|
|
1339
1339
|
|
|
1340
1340
|
it('moveFile re-judges with both locks held, and both release untouched', async () => {
|
|
1341
1341
|
await fs.writeFile(path.join(root, DRAFT), WITH_PHOENIX);
|
|
1342
|
-
const
|
|
1343
|
-
await expectRefusedUnderLock(layer(
|
|
1342
|
+
const versionControl = racingVersionControl(deletePhoenix);
|
|
1343
|
+
await expectRefusedUnderLock(layer(versionControl).moveFile(DRAFT, ROLES, { overwrite: true }), versionControl, 2);
|
|
1344
1344
|
expect(await fs.readFile(path.join(root, DRAFT), 'utf-8')).toBe(WITH_PHOENIX);
|
|
1345
1345
|
});
|
|
1346
1346
|
|
|
1347
1347
|
it('a members-only write whose roles all survive the wait still lands', async () => {
|
|
1348
|
-
const
|
|
1349
|
-
await layer(
|
|
1348
|
+
const versionControl = racingVersionControl(() => fs.writeFile(path.join(root, ROLES), `${WITH_PHOENIX} - q@x.eu\n`));
|
|
1349
|
+
await layer(versionControl).writeFile(ROLES, `${CURRENT} - b@x.eu\n`);
|
|
1350
1350
|
expect(await fs.readFile(path.join(root, ROLES), 'utf-8')).toBe(`${CURRENT} - b@x.eu\n`);
|
|
1351
|
-
expect(
|
|
1351
|
+
expect(versionControl.releaseLock).toHaveBeenCalledTimes(1);
|
|
1352
1352
|
});
|
|
1353
1353
|
|
|
1354
1354
|
/** A validator that passes before the lock and refuses under it by throwing a bare string. */
|
|
@@ -1363,36 +1363,36 @@ describe('LockingFilesystem — the agent roles.yaml gate covers every op that l
|
|
|
1363
1363
|
}
|
|
1364
1364
|
|
|
1365
1365
|
it('a check that throws a non-Error still releases untouched, and the caller gets that value', async () => {
|
|
1366
|
-
const
|
|
1366
|
+
const versionControl = makeVersionControl();
|
|
1367
1367
|
const fsLayer = new LockingFilesystem(
|
|
1368
1368
|
{ basePath: root, contained: true },
|
|
1369
|
-
{
|
|
1369
|
+
{ versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB, validateWrite: refusesUnderLockWith('refused') },
|
|
1370
1370
|
);
|
|
1371
1371
|
await expect(fsLayer.writeFile(ROLES, CURRENT)).rejects.toBe('refused');
|
|
1372
|
-
expect(
|
|
1373
|
-
expect(
|
|
1372
|
+
expect(versionControl.releaseLockUntouched).toHaveBeenCalledTimes(1);
|
|
1373
|
+
expect(versionControl.releaseLockNoCommit).not.toHaveBeenCalled();
|
|
1374
1374
|
expect(await fs.readFile(path.join(root, ROLES), 'utf-8')).toBe(WITH_PHOENIX);
|
|
1375
1375
|
});
|
|
1376
1376
|
|
|
1377
1377
|
it('a move whose check throws a non-Error releases BOTH locks untouched', async () => {
|
|
1378
|
-
const
|
|
1378
|
+
const versionControl = makeVersionControl();
|
|
1379
1379
|
const fsLayer = new LockingFilesystem(
|
|
1380
1380
|
{ basePath: root, contained: true },
|
|
1381
|
-
{
|
|
1381
|
+
{ versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB, validateWrite: refusesUnderLockWith('refused') },
|
|
1382
1382
|
);
|
|
1383
1383
|
await expect(fsLayer.moveFile(DRAFT, ROLES, { overwrite: true })).rejects.toBe('refused');
|
|
1384
|
-
expect(
|
|
1385
|
-
expect(
|
|
1384
|
+
expect(versionControl.releaseLockUntouched).toHaveBeenCalledTimes(2);
|
|
1385
|
+
expect(versionControl.releaseLockNoCommit).not.toHaveBeenCalled();
|
|
1386
1386
|
expect(await fs.readFile(path.join(root, DRAFT), 'utf-8')).toBe(WITH_PHOENIX);
|
|
1387
1387
|
});
|
|
1388
1388
|
});
|
|
1389
1389
|
|
|
1390
1390
|
it('a copy elsewhere never reads its source for a validator that does not claim the destination', async () => {
|
|
1391
|
-
const
|
|
1391
|
+
const versionControl = makeVersionControl();
|
|
1392
1392
|
const validateWrite = Object.assign(vi.fn(), { appliesTo: vi.fn(() => false) });
|
|
1393
1393
|
const fsLayer = new LockingFilesystem(
|
|
1394
1394
|
{ basePath: root, contained: true },
|
|
1395
|
-
{
|
|
1395
|
+
{ versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB, validateWrite },
|
|
1396
1396
|
);
|
|
1397
1397
|
await fsLayer.copyFile(DRAFT, `${KB}/KnowledgeBase/copy.yaml`);
|
|
1398
1398
|
expect(validateWrite.appliesTo).toHaveBeenCalledWith(`${KB}/KnowledgeBase/copy.yaml`);
|
|
@@ -1421,7 +1421,7 @@ describe('LockingFilesystem — a destination that is taken is refused, never re
|
|
|
1421
1421
|
await fs.writeFile(onDisk(SOURCE), 'source');
|
|
1422
1422
|
fsLayer = new LockingFilesystem(
|
|
1423
1423
|
{ basePath: root, contained: true },
|
|
1424
|
-
{
|
|
1424
|
+
{ versionControl: makeVersionControl(), workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB },
|
|
1425
1425
|
);
|
|
1426
1426
|
});
|
|
1427
1427
|
|
|
@@ -1463,10 +1463,10 @@ describe('LockingFilesystem — a destination that is taken is refused, never re
|
|
|
1463
1463
|
* every lock it unwinds releases untouched.
|
|
1464
1464
|
*/
|
|
1465
1465
|
it('releases UNTOUCHED when the destination is taken, so a lost race cannot eat the winner', async () => {
|
|
1466
|
-
const
|
|
1466
|
+
const versionControl = makeVersionControl();
|
|
1467
1467
|
const layer = new LockingFilesystem(
|
|
1468
1468
|
{ basePath: root, contained: true },
|
|
1469
|
-
{
|
|
1469
|
+
{ versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB },
|
|
1470
1470
|
);
|
|
1471
1471
|
await fs.writeFile(onDisk(TAKEN), 'the winner of the race');
|
|
1472
1472
|
|
|
@@ -1475,10 +1475,10 @@ describe('LockingFilesystem — a destination that is taken is refused, never re
|
|
|
1475
1475
|
// Both ends of the move: the destination is the one holding the winner's
|
|
1476
1476
|
// bytes, and the source was not written either.
|
|
1477
1477
|
for (const p of [SOURCE, TAKEN]) {
|
|
1478
|
-
expect(
|
|
1478
|
+
expect(versionControl.releaseLockUntouched, p).toHaveBeenCalledWith('ws-feat', 'feat', p, USER);
|
|
1479
1479
|
}
|
|
1480
|
-
expect(
|
|
1481
|
-
expect(
|
|
1480
|
+
expect(versionControl.releaseLockNoCommit).not.toHaveBeenCalled();
|
|
1481
|
+
expect(versionControl.releaseLock).not.toHaveBeenCalled();
|
|
1482
1482
|
expect(await fs.readFile(onDisk(TAKEN), 'utf-8')).toBe('the winner of the race');
|
|
1483
1483
|
});
|
|
1484
1484
|
|
|
@@ -1489,10 +1489,10 @@ describe('LockingFilesystem — a destination that is taken is refused, never re
|
|
|
1489
1489
|
* than discarding the winner's queued file.
|
|
1490
1490
|
*/
|
|
1491
1491
|
it('an exclusive write onto a taken name is the same refusal, and releases UNTOUCHED', async () => {
|
|
1492
|
-
const
|
|
1492
|
+
const versionControl = makeVersionControl();
|
|
1493
1493
|
const layer = new LockingFilesystem(
|
|
1494
1494
|
{ basePath: root, contained: true },
|
|
1495
|
-
{
|
|
1495
|
+
{ versionControl, workspaceId: 'ws-feat', branch: 'feat', user: USER, kbDirName: KB },
|
|
1496
1496
|
);
|
|
1497
1497
|
await fs.writeFile(onDisk(TAKEN), 'the winner of the race');
|
|
1498
1498
|
|
|
@@ -1501,9 +1501,9 @@ describe('LockingFilesystem — a destination that is taken is refused, never re
|
|
|
1501
1501
|
message: 'A file named taken.md already exists in KnowledgeBase.',
|
|
1502
1502
|
});
|
|
1503
1503
|
|
|
1504
|
-
expect(
|
|
1505
|
-
expect(
|
|
1506
|
-
expect(
|
|
1504
|
+
expect(versionControl.releaseLockUntouched).toHaveBeenCalledWith('ws-feat', 'feat', TAKEN, USER);
|
|
1505
|
+
expect(versionControl.releaseLockNoCommit).not.toHaveBeenCalled();
|
|
1506
|
+
expect(versionControl.releaseLock).not.toHaveBeenCalled();
|
|
1507
1507
|
expect(await fs.readFile(onDisk(TAKEN), 'utf-8')).toBe('the winner of the race');
|
|
1508
1508
|
});
|
|
1509
1509
|
|