@bevel-software/platform-core-backend 0.29.0 → 0.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agent-guide/access-control.md +49 -4
- package/agent-guide/html-views.md +76 -2
- package/dist/__tests__/test-repositories.d.ts +3 -0
- package/dist/__tests__/test-repositories.d.ts.map +1 -0
- package/dist/__tests__/test-repositories.js +18 -0
- package/dist/__tests__/test-repositories.js.map +1 -0
- package/dist/core/catalog-revision.js +3 -3
- package/dist/core/create-core-server.d.ts.map +1 -1
- package/dist/core/create-core-server.js +3 -2
- package/dist/core/create-core-server.js.map +1 -1
- package/dist/core/create-core-services.d.ts +3 -0
- package/dist/core/create-core-services.d.ts.map +1 -1
- package/dist/core/create-core-services.js +35 -5
- package/dist/core/create-core-services.js.map +1 -1
- package/dist/core/lifecycle.d.ts +8 -0
- package/dist/core/lifecycle.d.ts.map +1 -1
- package/dist/core/lifecycle.js +4 -0
- package/dist/core/lifecycle.js.map +1 -1
- package/dist/modules/access/access-control.interface.d.ts +20 -0
- package/dist/modules/access/access-control.interface.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.d.ts +32 -1
- package/dist/modules/access/access-control.service.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.js +135 -17
- package/dist/modules/access/access-control.service.js.map +1 -1
- package/dist/modules/access/access.routes.d.ts.map +1 -1
- package/dist/modules/access/access.routes.js +20 -6
- package/dist/modules/access/access.routes.js.map +1 -1
- package/dist/modules/agent-instructions/shared-file-rules.d.ts +1 -1
- package/dist/modules/agent-instructions/shared-file-rules.js +1 -1
- package/dist/modules/audit/agent-audit.service.d.ts.map +1 -1
- package/dist/modules/audit/agent-audit.service.js +66 -6
- package/dist/modules/audit/agent-audit.service.js.map +1 -1
- package/dist/modules/audit/audit.contract.d.ts +52 -8
- package/dist/modules/audit/audit.contract.d.ts.map +1 -1
- package/dist/modules/audit/audit.contract.js +37 -1
- package/dist/modules/audit/audit.contract.js.map +1 -1
- package/dist/modules/audit/audit.routes.d.ts +1 -1
- package/dist/modules/audit/audit.routes.d.ts.map +1 -1
- package/dist/modules/audit/audit.routes.js +15 -13
- package/dist/modules/audit/audit.routes.js.map +1 -1
- package/dist/modules/audit/request-audit.d.ts +21 -10
- package/dist/modules/audit/request-audit.d.ts.map +1 -1
- package/dist/modules/audit/request-audit.js +26 -10
- package/dist/modules/audit/request-audit.js.map +1 -1
- package/dist/modules/database/core-schema.d.ts +22 -3
- package/dist/modules/database/core-schema.d.ts.map +1 -1
- package/dist/modules/database/core-schema.js +15 -6
- package/dist/modules/database/core-schema.js.map +1 -1
- package/dist/modules/diff/diff.service.d.ts.map +1 -1
- package/dist/modules/diff/diff.service.js +7 -1
- package/dist/modules/diff/diff.service.js.map +1 -1
- package/dist/modules/kb-fs/git-dir.d.ts +21 -0
- package/dist/modules/kb-fs/git-dir.d.ts.map +1 -0
- package/dist/modules/kb-fs/git-dir.js +58 -0
- package/dist/modules/kb-fs/git-dir.js.map +1 -0
- package/dist/modules/kb-fs/index.d.ts +3 -0
- package/dist/modules/kb-fs/index.d.ts.map +1 -1
- package/dist/modules/kb-fs/index.js +3 -1
- package/dist/modules/kb-fs/index.js.map +1 -1
- package/dist/modules/kb-fs/locking-filesystem.d.ts.map +1 -1
- package/dist/modules/kb-fs/locking-filesystem.js +3 -12
- package/dist/modules/kb-fs/locking-filesystem.js.map +1 -1
- package/dist/modules/kb-fs/mutex.d.ts +11 -0
- package/dist/modules/kb-fs/mutex.d.ts.map +1 -1
- package/dist/modules/kb-fs/mutex.js +14 -0
- package/dist/modules/kb-fs/mutex.js.map +1 -1
- package/dist/modules/kb-fs/ref-files.d.ts +63 -0
- package/dist/modules/kb-fs/ref-files.d.ts.map +1 -0
- package/dist/modules/kb-fs/ref-files.js +249 -0
- package/dist/modules/kb-fs/ref-files.js.map +1 -0
- package/dist/modules/mcp/mcp-auth.middleware.d.ts +14 -0
- package/dist/modules/mcp/mcp-auth.middleware.d.ts.map +1 -1
- package/dist/modules/mcp/mcp-auth.middleware.js +4 -0
- package/dist/modules/mcp/mcp-auth.middleware.js.map +1 -1
- package/dist/modules/mcp/mcp.routes.d.ts +5 -0
- package/dist/modules/mcp/mcp.routes.d.ts.map +1 -1
- package/dist/modules/mcp/mcp.routes.js +67 -0
- package/dist/modules/mcp/mcp.routes.js.map +1 -1
- package/dist/modules/mcp/mcp.service.d.ts +107 -27
- package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
- package/dist/modules/mcp/mcp.service.js +257 -50
- package/dist/modules/mcp/mcp.service.js.map +1 -1
- package/dist/modules/mcp/request-context.d.ts +25 -0
- package/dist/modules/mcp/request-context.d.ts.map +1 -0
- package/dist/modules/mcp/request-context.js +11 -0
- package/dist/modules/mcp/request-context.js.map +1 -0
- package/dist/modules/mcp/shared-definitions.d.ts +56 -0
- package/dist/modules/mcp/shared-definitions.d.ts.map +1 -0
- package/dist/modules/mcp/shared-definitions.js +117 -0
- package/dist/modules/mcp/shared-definitions.js.map +1 -0
- package/dist/modules/mcp/surface-cache.d.ts +70 -0
- package/dist/modules/mcp/surface-cache.d.ts.map +1 -0
- package/dist/modules/mcp/surface-cache.js +104 -0
- package/dist/modules/mcp/surface-cache.js.map +1 -0
- package/dist/modules/plugins/teams.routes.d.ts +4 -0
- package/dist/modules/plugins/teams.routes.d.ts.map +1 -1
- package/dist/modules/plugins/teams.routes.js +9 -1
- package/dist/modules/plugins/teams.routes.js.map +1 -1
- package/dist/modules/tool-auth/internal-token.service.d.ts +1 -1
- package/dist/modules/tool-auth/tool-auth.middleware.d.ts +1 -2
- package/dist/modules/tool-auth/tool-auth.middleware.d.ts.map +1 -1
- package/dist/modules/tool-auth/tool-auth.middleware.js +1 -2
- package/dist/modules/tool-auth/tool-auth.middleware.js.map +1 -1
- package/dist/modules/tool-helpers/tool-context.d.ts.map +1 -1
- package/dist/modules/tool-helpers/tool-context.js +33 -12
- package/dist/modules/tool-helpers/tool-context.js.map +1 -1
- package/dist/modules/tool-helpers/tool-handler.d.ts +2 -2
- package/dist/modules/tool-helpers/tool.contract.d.ts +10 -2
- package/dist/modules/tool-helpers/tool.contract.d.ts.map +1 -1
- package/dist/modules/tool-helpers/tool.contract.js.map +1 -1
- package/dist/modules/workflow/agent-tools/change-request-read.tools.d.ts.map +1 -1
- package/dist/modules/workflow/agent-tools/change-request-read.tools.js +3 -14
- package/dist/modules/workflow/agent-tools/change-request-read.tools.js.map +1 -1
- package/dist/modules/workflow/agent-tools/workflow.tools.d.ts +18 -1
- package/dist/modules/workflow/agent-tools/workflow.tools.d.ts.map +1 -1
- package/dist/modules/workflow/agent-tools/workflow.tools.js +453 -2
- package/dist/modules/workflow/agent-tools/workflow.tools.js.map +1 -1
- package/dist/modules/workflow/git/git.service.d.ts +142 -4
- package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
- package/dist/modules/workflow/git/git.service.js +645 -128
- package/dist/modules/workflow/git/git.service.js.map +1 -1
- package/dist/modules/workflow/read-on-branch.d.ts +35 -0
- package/dist/modules/workflow/read-on-branch.d.ts.map +1 -0
- package/dist/modules/workflow/read-on-branch.js +58 -0
- package/dist/modules/workflow/read-on-branch.js.map +1 -0
- package/dist/modules/workflow/workflow-hooks.d.ts +1 -1
- package/dist/modules/workflow/workflow.routes.d.ts.map +1 -1
- package/dist/modules/workflow/workflow.routes.js +22 -41
- package/dist/modules/workflow/workflow.routes.js.map +1 -1
- package/dist/modules/workflow/workflow.service.d.ts +48 -8
- package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
- package/dist/modules/workflow/workflow.service.js +163 -16
- package/dist/modules/workflow/workflow.service.js.map +1 -1
- package/dist/modules/workspace/agent-access.gate.d.ts +2 -2
- package/dist/modules/workspace/agent-access.gate.js +2 -2
- package/dist/modules/workspace/bounded-matcher.d.ts +79 -0
- package/dist/modules/workspace/bounded-matcher.d.ts.map +1 -0
- package/dist/modules/workspace/bounded-matcher.js +261 -0
- package/dist/modules/workspace/bounded-matcher.js.map +1 -0
- package/dist/modules/workspace/fetch-instant.d.ts +9 -0
- package/dist/modules/workspace/fetch-instant.d.ts.map +1 -0
- package/dist/modules/workspace/fetch-instant.js +13 -0
- package/dist/modules/workspace/fetch-instant.js.map +1 -0
- package/dist/modules/workspace/index.d.ts +9 -0
- package/dist/modules/workspace/index.d.ts.map +1 -1
- package/dist/modules/workspace/index.js +10 -0
- package/dist/modules/workspace/index.js.map +1 -1
- package/dist/modules/workspace/kb-repository.contract.d.ts +124 -0
- package/dist/modules/workspace/kb-repository.contract.d.ts.map +1 -0
- package/dist/modules/workspace/kb-repository.contract.js +19 -0
- package/dist/modules/workspace/kb-repository.contract.js.map +1 -0
- package/dist/modules/workspace/kb-repository.d.ts +236 -0
- package/dist/modules/workspace/kb-repository.d.ts.map +1 -0
- package/dist/modules/workspace/kb-repository.js +811 -0
- package/dist/modules/workspace/kb-repository.js.map +1 -0
- package/dist/modules/workspace/pattern-matcher.contract.d.ts +50 -0
- package/dist/modules/workspace/pattern-matcher.contract.d.ts.map +1 -0
- package/dist/modules/workspace/pattern-matcher.contract.js +27 -0
- package/dist/modules/workspace/pattern-matcher.contract.js.map +1 -0
- package/dist/modules/workspace/routine-write-policy.d.ts +0 -7
- package/dist/modules/workspace/routine-write-policy.d.ts.map +1 -1
- package/dist/modules/workspace/routine-write-policy.js +0 -5
- package/dist/modules/workspace/routine-write-policy.js.map +1 -1
- package/dist/modules/workspace/set-aside-clone.d.ts +40 -0
- package/dist/modules/workspace/set-aside-clone.d.ts.map +1 -1
- package/dist/modules/workspace/set-aside-clone.js +139 -0
- package/dist/modules/workspace/set-aside-clone.js.map +1 -1
- package/dist/modules/workspace/startup/kb-startup-runner.d.ts +99 -0
- package/dist/modules/workspace/startup/kb-startup-runner.d.ts.map +1 -1
- package/dist/modules/workspace/startup/kb-startup-runner.js +407 -29
- package/dist/modules/workspace/startup/kb-startup-runner.js.map +1 -1
- package/dist/modules/workspace/workspace.service.d.ts +68 -32
- package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.service.js +240 -161
- package/dist/modules/workspace/workspace.service.js.map +1 -1
- package/dist/modules/workspace/workspace.tools.d.ts +13 -4
- package/dist/modules/workspace/workspace.tools.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.tools.js +162 -336
- package/dist/modules/workspace/workspace.tools.js.map +1 -1
- package/dist/shared/git.contract.d.ts +7 -0
- package/dist/shared/git.contract.d.ts.map +1 -1
- package/dist/shared/git.contract.js +9 -0
- package/dist/shared/git.contract.js.map +1 -1
- package/dist/shared/no-link-below.d.ts +10 -0
- package/dist/shared/no-link-below.d.ts.map +1 -0
- package/dist/shared/no-link-below.js +30 -0
- package/dist/shared/no-link-below.js.map +1 -0
- package/dist/test-setup.js +4 -0
- package/dist/test-setup.js.map +1 -1
- package/migrations/0018_agent_events_page.sql +11 -0
- package/migrations/meta/0018_snapshot.json +2429 -0
- package/migrations/meta/_journal.json +8 -1
- package/package.json +3 -3
- package/src/__tests__/test-repositories.ts +22 -0
- package/src/__tests__/workspace-service.ts +69 -0
- package/src/core/__tests__/lifecycle.test.ts +16 -0
- package/src/core/catalog-revision.ts +3 -3
- package/src/core/create-core-server.ts +3 -2
- package/src/core/create-core-services.ts +39 -4
- package/src/core/lifecycle.ts +10 -0
- package/src/modules/access/__tests__/access-control.atref-cache.test.ts +144 -4
- package/src/modules/access/__tests__/access-control.group-read.test.ts +73 -0
- package/src/modules/access/__tests__/access.routes.group-grant.test.ts +66 -0
- package/src/modules/access/__tests__/access.routes.unknown-email.test.ts +2 -0
- package/src/modules/access/access-control.interface.ts +22 -0
- package/src/modules/access/access-control.service.ts +138 -15
- package/src/modules/access/access.routes.ts +22 -6
- package/src/modules/agent-guide/__tests__/agent-guide.test.ts +22 -1
- package/src/modules/agent-instructions/shared-file-rules.ts +1 -1
- package/src/modules/audit/__tests__/agent-audit.service.test.ts +78 -1
- package/src/modules/audit/__tests__/audit.routes.test.ts +43 -5
- package/src/modules/audit/agent-audit.service.ts +66 -6
- package/src/modules/audit/audit.contract.ts +62 -10
- package/src/modules/audit/audit.routes.ts +14 -12
- package/src/modules/audit/request-audit.ts +28 -13
- package/src/modules/database/core-schema.ts +15 -6
- package/src/modules/diff/__tests__/diff.service.seed-atomicity.test.ts +2 -1
- package/src/modules/diff/__tests__/diff.service.test.ts +2 -1
- package/src/modules/diff/__tests__/diff.service.worktree.test.ts +76 -0
- package/src/modules/diff/diff.service.ts +6 -4
- package/src/modules/kb-fs/__tests__/git-dir.test.ts +77 -0
- package/src/modules/kb-fs/__tests__/mutex.canonical.test.ts +57 -0
- package/src/modules/kb-fs/__tests__/ref-files.test.ts +338 -0
- package/src/modules/kb-fs/git-dir.ts +53 -0
- package/src/modules/kb-fs/index.ts +3 -0
- package/src/modules/kb-fs/locking-filesystem.ts +3 -12
- package/src/modules/kb-fs/mutex.ts +16 -0
- package/src/modules/kb-fs/ref-files.ts +279 -0
- package/src/modules/mcp/__tests__/mcp-routes-harness.ts +7 -1
- package/src/modules/mcp/__tests__/mcp.routes.page.test.ts +115 -0
- package/src/modules/mcp/__tests__/mcp.surface-cache.test.ts +521 -0
- package/src/modules/mcp/__tests__/shared-definitions.test.ts +28 -0
- package/src/modules/mcp/__tests__/surface-cache.test.ts +127 -0
- package/src/modules/mcp/mcp-auth.middleware.ts +21 -0
- package/src/modules/mcp/mcp.routes.ts +68 -0
- package/src/modules/mcp/mcp.service.ts +315 -51
- package/src/modules/mcp/request-context.ts +34 -0
- package/src/modules/mcp/shared-definitions.ts +121 -0
- package/src/modules/mcp/surface-cache.ts +154 -0
- package/src/modules/plugins/__tests__/teams.routes.test.ts +55 -0
- package/src/modules/plugins/teams.routes.ts +14 -1
- package/src/modules/settings/__tests__/setup.routes.layout-phase.test.ts +2 -2
- package/src/modules/settings/__tests__/setup.routes.managed-phase.test.ts +2 -2
- package/src/modules/tool-auth/internal-token.service.ts +1 -1
- package/src/modules/tool-auth/tool-auth.middleware.ts +1 -2
- package/src/modules/tool-helpers/__tests__/branch-declaration.test.ts +2 -2
- package/src/modules/tool-helpers/tool-context.ts +37 -18
- package/src/modules/tool-helpers/tool-handler.ts +2 -2
- package/src/modules/tool-helpers/tool.contract.ts +10 -2
- package/src/modules/tool-registry/__tests__/own-tool-schemas.test.ts +1 -1
- package/src/modules/tool-registry/__tests__/tool-description-length.test.ts +7 -2
- package/src/modules/workflow/__tests__/workflow.routes.history-read-gate.test.ts +23 -12
- package/src/modules/workflow/__tests__/workflow.service.facade.test.ts +250 -4
- package/src/modules/workflow/agent-tools/__tests__/workflow.tools.test.ts +464 -4
- package/src/modules/workflow/agent-tools/change-request-read.tools.ts +4 -22
- package/src/modules/workflow/agent-tools/workflow.tools.ts +462 -1
- package/src/modules/workflow/git/__tests__/git.service.changedPathsMemo.test.ts +175 -0
- package/src/modules/workflow/git/__tests__/git.service.diffFileBetweenBranches.test.ts +76 -11
- package/src/modules/workflow/git/__tests__/git.service.mergeIntoBranch.test.ts +721 -0
- package/src/modules/workflow/git/__tests__/git.service.worktrees.test.ts +284 -0
- package/src/modules/workflow/git/git.service.ts +719 -126
- package/src/modules/workflow/read-on-branch.ts +79 -0
- package/src/modules/workflow/workflow-hooks.ts +1 -1
- package/src/modules/workflow/workflow.routes.ts +21 -39
- package/src/modules/workflow/workflow.service.ts +203 -16
- package/src/modules/workspace/__tests__/agent-downloads.test.ts +2 -2
- package/src/modules/workspace/__tests__/bounded-matcher.test.ts +213 -0
- package/src/modules/workspace/__tests__/git-internals.security.test.ts +22 -7
- package/src/modules/workspace/__tests__/kb-repository.test.ts +481 -0
- package/src/modules/workspace/__tests__/rest-tool-arguments.test.ts +14 -0
- package/src/modules/workspace/__tests__/routine-write-policy.test.ts +0 -11
- package/src/modules/workspace/__tests__/workspace-paths-inside-the-repo.test.ts +2 -2
- package/src/modules/workspace/__tests__/workspace.routes.folder-zip-access.test.ts +2 -2
- package/src/modules/workspace/__tests__/workspace.routes.folders.test.ts +4 -3
- package/src/modules/workspace/__tests__/workspace.service.any-workspace-credentials.test.ts +2 -2
- package/src/modules/workspace/__tests__/workspace.service.ensure-remotes-fetched.test.ts +2 -2
- package/src/modules/workspace/__tests__/workspace.service.forget-clone-races.test.ts +12 -5
- package/src/modules/workspace/__tests__/workspace.service.list-cloned.test.ts +6 -6
- package/src/modules/workspace/__tests__/workspace.service.move-never-overwrites.test.ts +2 -1
- package/src/modules/workspace/__tests__/workspace.service.read-filter.test.ts +2 -1
- package/src/modules/workspace/__tests__/workspace.service.replaced-repository.test.ts +2 -1
- package/src/modules/workspace/__tests__/workspace.service.test.ts +24 -23
- package/src/modules/workspace/__tests__/workspace.service.unknown-branch.test.ts +2 -1
- package/src/modules/workspace/__tests__/workspace.service.worktrees.test.ts +620 -0
- package/src/modules/workspace/__tests__/workspace.tools.agents-file.test.ts +15 -1
- package/src/modules/workspace/__tests__/workspace.tools.branch-errors.test.ts +71 -5
- package/src/modules/workspace/__tests__/workspace.tools.test.ts +153 -298
- package/src/modules/workspace/agent-access.gate.ts +2 -2
- package/src/modules/workspace/bounded-matcher.ts +328 -0
- package/src/modules/workspace/fetch-instant.ts +13 -0
- package/src/modules/workspace/index.ts +23 -0
- package/src/modules/workspace/kb-repository.contract.ts +124 -0
- package/src/modules/workspace/kb-repository.ts +844 -0
- package/src/modules/workspace/pattern-matcher.contract.ts +69 -0
- package/src/modules/workspace/routine-write-policy.ts +0 -15
- package/src/modules/workspace/set-aside-clone.ts +140 -0
- package/src/modules/workspace/startup/__tests__/kb-startup-runner.degraded.test.ts +3 -2
- package/src/modules/workspace/startup/__tests__/kb-startup-runner.test.ts +12 -10
- package/src/modules/workspace/startup/__tests__/kb-startup-runner.worktrees.test.ts +613 -0
- package/src/modules/workspace/startup/kb-startup-runner.ts +422 -29
- package/src/modules/workspace/startup/steps/__tests__/steps.test.ts +4 -3
- package/src/modules/workspace/workspace.service.ts +247 -169
- package/src/modules/workspace/workspace.tools.ts +179 -365
- package/src/shared/__tests__/no-link-below.test.ts +45 -0
- package/src/shared/git.contract.ts +10 -0
- package/src/shared/no-link-below.ts +29 -0
- package/src/test-setup.ts +5 -0
|
@@ -3,9 +3,10 @@ import path from 'node:path';
|
|
|
3
3
|
import { logger } from '../../shared/logging.js';
|
|
4
4
|
const log = logger('access');
|
|
5
5
|
import { isAbsence } from '../../shared/fs.contract.js';
|
|
6
|
-
import { assertOneSpecPerLine, isGitTimeout } from '../../shared/git.contract.js';
|
|
6
|
+
import { assertOneSpecPerLine, isCommitSha, isGitTimeout } from '../../shared/git.contract.js';
|
|
7
7
|
import { hashEmail } from '../../shared/email-identity.js';
|
|
8
8
|
import { NodeGitRunner } from '../workflow/git/node-git-runner.js';
|
|
9
|
+
import { readRevSha } from '../kb-fs/index.js';
|
|
9
10
|
import { DEFAULT_KB_LAYOUT, PLUGIN_MANIFEST_FILE, isPersonalPluginDir, pluginIdentityOf, platformRestoreDestination, } from '@bevel-software/platform-shared';
|
|
10
11
|
import { AccessConfigError, AccessUnreadableError } from '../access-model/access-errors.js';
|
|
11
12
|
import { synthesizePluginPrincipals } from '../access-model/plugin-principals.js';
|
|
@@ -1143,6 +1144,24 @@ export class AccessControlService {
|
|
|
1143
1144
|
atRefNoRoles = new Set();
|
|
1144
1145
|
atRefInFlight = new Map();
|
|
1145
1146
|
static AT_REF_CACHE_MAX = 512;
|
|
1147
|
+
/**
|
|
1148
|
+
* A path's own access entries AT A COMMIT, keyed by commit + path. A blob
|
|
1149
|
+
* at a commit never changes, so neither does what it declares for itself:
|
|
1150
|
+
* no TTL, no invalidation, and no workspace in the key (every clone holds
|
|
1151
|
+
* the same objects). This is the read every at-ref gate makes after the
|
|
1152
|
+
* model: one `cat-file --batch` per call for the paths asked, which a
|
|
1153
|
+
* change-request listing made once per ref per listing for the same paths
|
|
1154
|
+
* at the same commits. Bounded FIFO; the bound only caps memory.
|
|
1155
|
+
*/
|
|
1156
|
+
ownEntriesAtCommit = new Map();
|
|
1157
|
+
static OWN_ENTRIES_AT_COMMIT_MAX = 16_384;
|
|
1158
|
+
/** Paths a cached model keeps of its commit's tree, at most; a larger tree is not kept. */
|
|
1159
|
+
static TREE_INDEX_MAX_PATHS = 20_000;
|
|
1160
|
+
/** Paths held across every cached model's tree, at most; past it the oldest models let their tree go. */
|
|
1161
|
+
static TREE_INDEX_BUDGET = 500_000;
|
|
1162
|
+
treeIndexMaxPaths;
|
|
1163
|
+
treeIndexBudget;
|
|
1164
|
+
treePathsHeld = 0;
|
|
1146
1165
|
/**
|
|
1147
1166
|
* Canonicalised deployment-owner emails (see `AccessModel.deploymentOwners`).
|
|
1148
1167
|
* Held on the service rather than read per-model because it comes from the
|
|
@@ -1171,13 +1190,22 @@ export class AccessControlService {
|
|
|
1171
1190
|
* `deploymentOwners`: the fixtures that construct this service directly
|
|
1172
1191
|
* run on the default layout.
|
|
1173
1192
|
*/
|
|
1174
|
-
kb = { layout: DEFAULT_KB_LAYOUT }
|
|
1193
|
+
kb = { layout: DEFAULT_KB_LAYOUT },
|
|
1194
|
+
/**
|
|
1195
|
+
* How much of the commits' trees the at-ref models may hold (see
|
|
1196
|
+
* `TREE_INDEX_MAX_PATHS`, `TREE_INDEX_BUDGET`). The defaults are the
|
|
1197
|
+
* deployment's; a suite lowers them to reach the bounds with a small
|
|
1198
|
+
* repository.
|
|
1199
|
+
*/
|
|
1200
|
+
limits = {}) {
|
|
1175
1201
|
this.workspaceService = workspaceService;
|
|
1176
1202
|
this.kbDirName = kbDirName;
|
|
1177
1203
|
this.disk = disk;
|
|
1178
1204
|
this.gitRunner = gitRunner;
|
|
1179
1205
|
this.kb = kb;
|
|
1180
1206
|
this.deploymentOwners = new Set(deploymentOwners.filter(Boolean).map((e) => canonicalEmail(e)));
|
|
1207
|
+
this.treeIndexMaxPaths = limits.treeIndexMaxPaths ?? AccessControlService.TREE_INDEX_MAX_PATHS;
|
|
1208
|
+
this.treeIndexBudget = limits.treeIndexBudget ?? AccessControlService.TREE_INDEX_BUDGET;
|
|
1181
1209
|
}
|
|
1182
1210
|
/**
|
|
1183
1211
|
* Drop a workspace's cached model + frontmatter memo. Call after operations
|
|
@@ -1292,6 +1320,24 @@ export class AccessControlService {
|
|
|
1292
1320
|
});
|
|
1293
1321
|
return result;
|
|
1294
1322
|
}
|
|
1323
|
+
async groupsOfUser(workspaceId, userEmail) {
|
|
1324
|
+
const model = await this.loadModel(workspaceId);
|
|
1325
|
+
const email = canonicalEmail(userEmail);
|
|
1326
|
+
const names = [];
|
|
1327
|
+
for (const principal of model.roles.byCanonical.values()) {
|
|
1328
|
+
if (principal.kind === 'group' && principal.emails.has(email))
|
|
1329
|
+
names.push(principal.displayName);
|
|
1330
|
+
}
|
|
1331
|
+
return names;
|
|
1332
|
+
}
|
|
1333
|
+
async visibleGroups(workspaceId, userEmail, groups) {
|
|
1334
|
+
if (groups.length === 0)
|
|
1335
|
+
return [];
|
|
1336
|
+
if (await this.holdsAdminRootWrite(workspaceId, userEmail))
|
|
1337
|
+
return [...groups];
|
|
1338
|
+
const mine = new Set(await this.groupsOfUser(workspaceId, userEmail));
|
|
1339
|
+
return groups.filter((g) => mine.has(g));
|
|
1340
|
+
}
|
|
1295
1341
|
async canWriteBatch(workspaceId, userEmail, relativePaths) {
|
|
1296
1342
|
const model = await this.loadModel(workspaceId);
|
|
1297
1343
|
const repoDir = await this.repoDir(workspaceId);
|
|
@@ -1670,7 +1716,7 @@ export class AccessControlService {
|
|
|
1670
1716
|
if (!loaded)
|
|
1671
1717
|
return null;
|
|
1672
1718
|
const repoDir = await this.repoDir(workspaceId);
|
|
1673
|
-
const own = await this.readOwnEntriesAtRef(repoDir, loaded.resolvedRef, relativePath);
|
|
1719
|
+
const own = await this.readOwnEntriesAtRef(repoDir, loaded.resolvedRef, relativePath, loaded.tree);
|
|
1674
1720
|
return hasPermissionResolved(loaded.model, 'write', userEmail, relativePath, own);
|
|
1675
1721
|
}
|
|
1676
1722
|
async canReadAtRef(workspaceId, ref, userEmail, relativePath) {
|
|
@@ -1678,7 +1724,7 @@ export class AccessControlService {
|
|
|
1678
1724
|
if (!loaded)
|
|
1679
1725
|
return null;
|
|
1680
1726
|
const repoDir = await this.repoDir(workspaceId);
|
|
1681
|
-
const own = await this.readOwnEntriesAtRef(repoDir, loaded.resolvedRef, relativePath);
|
|
1727
|
+
const own = await this.readOwnEntriesAtRef(repoDir, loaded.resolvedRef, relativePath, loaded.tree);
|
|
1682
1728
|
return canReadResolved(loaded.model, userEmail, relativePath, own);
|
|
1683
1729
|
}
|
|
1684
1730
|
async canWriteBatchAtRef(workspaceId, ref, userEmail, relativePaths) {
|
|
@@ -1698,7 +1744,7 @@ export class AccessControlService {
|
|
|
1698
1744
|
const repoDir = await this.repoDir(workspaceId);
|
|
1699
1745
|
// One `git cat-file --batch` for the whole path set — a per-path `git
|
|
1700
1746
|
// show` spawn made CR owner-routing take minutes on large change sets.
|
|
1701
|
-
const owns = await this.readOwnEntriesAtRefBatch(repoDir, loaded.resolvedRef, relativePaths);
|
|
1747
|
+
const owns = await this.readOwnEntriesAtRefBatch(repoDir, loaded.resolvedRef, relativePaths, loaded.tree);
|
|
1702
1748
|
const result = new Map();
|
|
1703
1749
|
for (const p of relativePaths) {
|
|
1704
1750
|
const machineOwned = machineAnswers.get(p);
|
|
@@ -1715,7 +1761,7 @@ export class AccessControlService {
|
|
|
1715
1761
|
return null;
|
|
1716
1762
|
const repoDir = await this.repoDir(workspaceId);
|
|
1717
1763
|
// One `git cat-file --batch` for the whole set — see canWriteBatchAtRef.
|
|
1718
|
-
const owns = await this.readOwnEntriesAtRefBatch(repoDir, loaded.resolvedRef, relativePaths);
|
|
1764
|
+
const owns = await this.readOwnEntriesAtRefBatch(repoDir, loaded.resolvedRef, relativePaths, loaded.tree);
|
|
1719
1765
|
const result = new Map();
|
|
1720
1766
|
for (const p of relativePaths) {
|
|
1721
1767
|
result.set(p, canReadResolved(loaded.model, userEmail, p, owns.get(p) ?? null));
|
|
@@ -1734,7 +1780,7 @@ export class AccessControlService {
|
|
|
1734
1780
|
if (!loaded)
|
|
1735
1781
|
return null;
|
|
1736
1782
|
const repoDir = await this.repoDir(workspaceId);
|
|
1737
|
-
const own = await this.readOwnEntriesAtRef(repoDir, loaded.resolvedRef, relativePath);
|
|
1783
|
+
const own = await this.readOwnEntriesAtRef(repoDir, loaded.resolvedRef, relativePath, loaded.tree);
|
|
1738
1784
|
return eligibleHoldersResolved(loaded.model, 'write', relativePath, own);
|
|
1739
1785
|
}
|
|
1740
1786
|
async holdsAdminRootWrite(workspaceId, userEmail) {
|
|
@@ -1826,7 +1872,7 @@ export class AccessControlService {
|
|
|
1826
1872
|
return null;
|
|
1827
1873
|
const repoDir = await this.repoDir(workspaceId);
|
|
1828
1874
|
// One `git cat-file --batch` for the whole path set — see canWriteBatchAtRef.
|
|
1829
|
-
const owns = await this.readOwnEntriesAtRefBatch(repoDir, loaded.resolvedRef, relativePaths);
|
|
1875
|
+
const owns = await this.readOwnEntriesAtRefBatch(repoDir, loaded.resolvedRef, relativePaths, loaded.tree);
|
|
1830
1876
|
for (const p of relativePaths) {
|
|
1831
1877
|
if (result.has(p))
|
|
1832
1878
|
continue;
|
|
@@ -2021,9 +2067,14 @@ export class AccessControlService {
|
|
|
2021
2067
|
* grant themselves rights by editing a file's frontmatter in a branch the
|
|
2022
2068
|
* gate isn't reading from.
|
|
2023
2069
|
*/
|
|
2024
|
-
async readOwnEntriesAtRef(repoDir, ref, relativePath) {
|
|
2070
|
+
async readOwnEntriesAtRef(repoDir, ref, relativePath, tree = null) {
|
|
2025
2071
|
if (relativePath === 'roles.yaml')
|
|
2026
2072
|
return null;
|
|
2073
|
+
// At a commit (what every gate passes: `resolvedRef`), the batched read
|
|
2074
|
+
// and its memo answer; a single `git show` stays for a symbolic ref.
|
|
2075
|
+
if (isCommitSha(ref)) {
|
|
2076
|
+
return (await this.readOwnEntriesAtRefBatch(repoDir, ref, [relativePath], tree)).get(relativePath) ?? null;
|
|
2077
|
+
}
|
|
2027
2078
|
const text = await this.showAtRef(repoDir, ref, relativePath);
|
|
2028
2079
|
if (text === null)
|
|
2029
2080
|
return null;
|
|
@@ -2039,13 +2090,31 @@ export class AccessControlService {
|
|
|
2039
2090
|
* `access.md`/`roles.yaml` (which carry no own-entries) resolve to null,
|
|
2040
2091
|
* mirroring the single-path helper.
|
|
2041
2092
|
*/
|
|
2042
|
-
async readOwnEntriesAtRefBatch(repoDir, ref, relativePaths) {
|
|
2093
|
+
async readOwnEntriesAtRefBatch(repoDir, ref, relativePaths, tree = null) {
|
|
2043
2094
|
const result = new Map();
|
|
2044
2095
|
const wanted = [];
|
|
2096
|
+
const memo = isCommitSha(ref) ? this.ownEntriesAtCommit : null;
|
|
2045
2097
|
for (const p of relativePaths) {
|
|
2046
|
-
if (p === 'roles.yaml')
|
|
2098
|
+
if (p === 'roles.yaml') {
|
|
2099
|
+
result.set(p, null);
|
|
2100
|
+
continue;
|
|
2101
|
+
}
|
|
2102
|
+
const key = `${ref}\0${p}`;
|
|
2103
|
+
if (memo?.has(key)) {
|
|
2104
|
+
result.set(p, memo.get(key) ?? null);
|
|
2105
|
+
continue;
|
|
2106
|
+
}
|
|
2107
|
+
// A path the commit's tree does not carry has no own entries, as a
|
|
2108
|
+
// fact about the commit: remembered, and not read. (A change request
|
|
2109
|
+
// that adds a file asks about it at the base, where it does not exist
|
|
2110
|
+
// yet; one that deletes a file asks at the head.)
|
|
2111
|
+
if (tree && !tree.has(p)) {
|
|
2047
2112
|
result.set(p, null);
|
|
2048
|
-
|
|
2113
|
+
if (memo)
|
|
2114
|
+
this.rememberOwnEntriesAtCommit(key, null);
|
|
2115
|
+
continue;
|
|
2116
|
+
}
|
|
2117
|
+
if (!wanted.includes(p))
|
|
2049
2118
|
wanted.push(p);
|
|
2050
2119
|
}
|
|
2051
2120
|
if (wanted.length === 0)
|
|
@@ -2053,13 +2122,30 @@ export class AccessControlService {
|
|
|
2053
2122
|
const texts = await catFileBatch(this.gitRunner, repoDir, wanted.map((p) => `${ref}:${p}`));
|
|
2054
2123
|
wanted.forEach((p, i) => {
|
|
2055
2124
|
const text = texts[i];
|
|
2056
|
-
if (text === null)
|
|
2125
|
+
if (text === null) {
|
|
2126
|
+
// Absent at the commit (when no tree was there to say so first), or
|
|
2127
|
+
// a blob git could not read: the batch says "missing" for both, and
|
|
2128
|
+
// only the first is a fact about the commit. Answered as no own
|
|
2129
|
+
// entries, as before, but never remembered.
|
|
2057
2130
|
result.set(p, null);
|
|
2058
|
-
|
|
2059
|
-
|
|
2131
|
+
return;
|
|
2132
|
+
}
|
|
2133
|
+
const own = isAccessMdPath(p) ? accessMdSelfEntries(text) : parseOwnAccessEntries(text);
|
|
2134
|
+
result.set(p, own);
|
|
2135
|
+
// What a blob declares for itself — including nothing — is a fact about the commit.
|
|
2136
|
+
if (memo)
|
|
2137
|
+
this.rememberOwnEntriesAtCommit(`${ref}\0${p}`, own);
|
|
2060
2138
|
});
|
|
2061
2139
|
return result;
|
|
2062
2140
|
}
|
|
2141
|
+
rememberOwnEntriesAtCommit(key, value) {
|
|
2142
|
+
this.ownEntriesAtCommit.set(key, value);
|
|
2143
|
+
if (this.ownEntriesAtCommit.size > AccessControlService.OWN_ENTRIES_AT_COMMIT_MAX) {
|
|
2144
|
+
const oldest = this.ownEntriesAtCommit.keys().next().value;
|
|
2145
|
+
if (oldest !== undefined)
|
|
2146
|
+
this.ownEntriesAtCommit.delete(oldest);
|
|
2147
|
+
}
|
|
2148
|
+
}
|
|
2063
2149
|
/**
|
|
2064
2150
|
* Resolve a candidate ref to one git accepts. Falls back to `origin/<ref>`
|
|
2065
2151
|
* for short branch names, matching the heuristic in
|
|
@@ -2115,6 +2201,7 @@ export class AccessControlService {
|
|
|
2115
2201
|
const { stdout } = await this.gitRunner.run(repoDir, ['ls-tree', '-r', '-z', '--name-only', ref]);
|
|
2116
2202
|
const out = [];
|
|
2117
2203
|
const pluginDirs = [];
|
|
2204
|
+
const all = [];
|
|
2118
2205
|
// The fixed-name sources the tree carries (roles.yaml, the groups
|
|
2119
2206
|
// files): a read of one of these that comes back empty is "absent"
|
|
2120
2207
|
// only when the tree does not name it.
|
|
@@ -2123,6 +2210,9 @@ export class AccessControlService {
|
|
|
2123
2210
|
for (const p of stdout.split('\0')) {
|
|
2124
2211
|
if (!p)
|
|
2125
2212
|
continue;
|
|
2213
|
+
// One past the cap is enough to know the tree is not kept.
|
|
2214
|
+
if (all.length <= this.treeIndexMaxPaths)
|
|
2215
|
+
all.push(p);
|
|
2126
2216
|
if (p === 'access.md' || p.endsWith('/access.md'))
|
|
2127
2217
|
out.push(p);
|
|
2128
2218
|
else if (p === 'roles.yaml' || p === SYNCED_GROUPS_YAML || p === GROUPS_YAML)
|
|
@@ -2131,7 +2221,7 @@ export class AccessControlService {
|
|
|
2131
2221
|
pluginDirs.push(p.slice(0, -(PLUGIN_MANIFEST_FILE.length + 1)));
|
|
2132
2222
|
}
|
|
2133
2223
|
}
|
|
2134
|
-
return { accessFiles: out, pluginDirs, named };
|
|
2224
|
+
return { accessFiles: out, pluginDirs, named, all };
|
|
2135
2225
|
}
|
|
2136
2226
|
catch {
|
|
2137
2227
|
return 'error';
|
|
@@ -2212,6 +2302,16 @@ export class AccessControlService {
|
|
|
2212
2302
|
async revParseCommit(repoDir, ref) {
|
|
2213
2303
|
if (!ref || ref.startsWith('-'))
|
|
2214
2304
|
return null;
|
|
2305
|
+
// The ref files first (`readRevSha`): the names git would try, in git's
|
|
2306
|
+
// order, read without a process. "Missing" from the files is what
|
|
2307
|
+
// rev-parse answers nothing for; "unreadable" — a spelling the reader
|
|
2308
|
+
// does not resolve, a tag it cannot peel, a file it could not read — is
|
|
2309
|
+
// git's to decide, as the authority.
|
|
2310
|
+
const fromFiles = await readRevSha(repoDir, ref);
|
|
2311
|
+
if (fromFiles === 'missing')
|
|
2312
|
+
return null;
|
|
2313
|
+
if (fromFiles !== 'unreadable')
|
|
2314
|
+
return fromFiles.sha;
|
|
2215
2315
|
try {
|
|
2216
2316
|
const { stdout } = await this.gitRunner.run(repoDir, [
|
|
2217
2317
|
'rev-parse',
|
|
@@ -2233,10 +2333,24 @@ export class AccessControlService {
|
|
|
2233
2333
|
}
|
|
2234
2334
|
rememberAtRef(key, value) {
|
|
2235
2335
|
this.atRefCache.set(key, value);
|
|
2336
|
+
this.treePathsHeld += value.tree?.size ?? 0;
|
|
2236
2337
|
if (this.atRefCache.size > AccessControlService.AT_REF_CACHE_MAX) {
|
|
2237
2338
|
const oldest = this.atRefCache.keys().next().value;
|
|
2238
|
-
if (oldest !== undefined)
|
|
2339
|
+
if (oldest !== undefined) {
|
|
2340
|
+
this.treePathsHeld -= this.atRefCache.get(oldest)?.tree?.size ?? 0;
|
|
2239
2341
|
this.atRefCache.delete(oldest);
|
|
2342
|
+
}
|
|
2343
|
+
}
|
|
2344
|
+
// The trees are a budget of their own, across every held model: past
|
|
2345
|
+
// it, the oldest models keep their model and let their tree go — the
|
|
2346
|
+
// gate still answers, absence is just no longer remembered for them.
|
|
2347
|
+
for (const held of this.atRefCache.values()) {
|
|
2348
|
+
if (this.treePathsHeld <= this.treeIndexBudget)
|
|
2349
|
+
break;
|
|
2350
|
+
if (!held.tree)
|
|
2351
|
+
continue;
|
|
2352
|
+
this.treePathsHeld -= held.tree.size;
|
|
2353
|
+
held.tree = null;
|
|
2240
2354
|
}
|
|
2241
2355
|
}
|
|
2242
2356
|
rememberNoRolesAtRef(key) {
|
|
@@ -2401,6 +2515,10 @@ export class AccessControlService {
|
|
|
2401
2515
|
deploymentOwners: this.deploymentOwners,
|
|
2402
2516
|
},
|
|
2403
2517
|
resolvedRef: commit,
|
|
2518
|
+
// The listing already named every path; held so an own-entries read
|
|
2519
|
+
// can tell absence from a lost blob, unless the tree is too large to
|
|
2520
|
+
// hold for every cached commit (then absence is simply not remembered).
|
|
2521
|
+
tree: listed.all.length <= this.treeIndexMaxPaths ? new Set(listed.all) : null,
|
|
2404
2522
|
};
|
|
2405
2523
|
}
|
|
2406
2524
|
}
|