@bevel-software/platform-core-backend 0.30.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/core/catalog-revision.js +3 -3
- package/dist/core/create-core-server.d.ts.map +1 -1
- package/dist/core/create-core-server.js +2 -1
- package/dist/core/create-core-server.js.map +1 -1
- package/dist/core/create-core-services.d.ts.map +1 -1
- package/dist/core/create-core-services.js +4 -2
- package/dist/core/create-core-services.js.map +1 -1
- package/dist/modules/access/access-control.service.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.js +1 -5
- package/dist/modules/access/access-control.service.js.map +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/kb-fs/index.d.ts +1 -0
- package/dist/modules/kb-fs/index.d.ts.map +1 -1
- package/dist/modules/kb-fs/index.js +1 -0
- 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/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/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.contract.d.ts +8 -0
- 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 +113 -4
- package/dist/modules/workflow/git/git.service.d.ts.map +1 -1
- package/dist/modules/workflow/git/git.service.js +470 -106
- 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.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/index.d.ts +2 -0
- package/dist/modules/workspace/index.d.ts.map +1 -1
- package/dist/modules/workspace/index.js +4 -0
- package/dist/modules/workspace/index.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/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/core/catalog-revision.ts +3 -3
- package/src/core/create-core-server.ts +2 -1
- package/src/core/create-core-services.ts +4 -2
- package/src/modules/access/access-control.service.ts +1 -6
- package/src/modules/agent-guide/__tests__/agent-guide.test.ts +22 -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/kb-fs/__tests__/mutex.canonical.test.ts +57 -0
- package/src/modules/kb-fs/index.ts +1 -0
- package/src/modules/kb-fs/locking-filesystem.ts +3 -12
- package/src/modules/kb-fs/mutex.ts +16 -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/tool-helpers/tool-context.ts +37 -18
- package/src/modules/tool-helpers/tool.contract.ts +8 -0
- package/src/modules/tool-registry/__tests__/own-tool-schemas.test.ts +1 -1
- package/src/modules/tool-registry/__tests__/tool-description-length.test.ts +1 -1
- 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.diffFileBetweenBranches.test.ts +76 -11
- package/src/modules/workflow/git/__tests__/git.service.mergeIntoBranch.test.ts +721 -0
- package/src/modules/workflow/git/git.service.ts +537 -106
- package/src/modules/workflow/read-on-branch.ts +79 -0
- package/src/modules/workflow/workflow.routes.ts +21 -39
- package/src/modules/workflow/workflow.service.ts +203 -16
- package/src/modules/workspace/index.ts +11 -0
- 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
|
@@ -31,6 +31,7 @@ import { promises as fs } from 'node:fs';
|
|
|
31
31
|
import { execFile } from 'node:child_process';
|
|
32
32
|
import { promisify } from 'node:util';
|
|
33
33
|
import { isFolderPlaceholder, folderPlaceholderPath, isPlatformRestoreShape, } from '@bevel-software/platform-shared';
|
|
34
|
+
import { readableAtCommits, ReadOnBranchDeniedError } from './read-on-branch.js';
|
|
34
35
|
import { and, desc, eq, inArray, isNotNull, isNull, lt, ne, or, sql } from 'drizzle-orm';
|
|
35
36
|
import { changeRequests } from '../database/schema.js';
|
|
36
37
|
import { pgErrorCode } from '../database/pg-error.js';
|
|
@@ -340,19 +341,25 @@ export class WorkflowService {
|
|
|
340
341
|
* opposite directions (`A -> B` and `B -> A`), and nothing is ever held
|
|
341
342
|
* while waiting for the other key.
|
|
342
343
|
*
|
|
343
|
-
* A PROTECTED
|
|
344
|
-
* delete a protected branch (`git.deleteBranch` throws
|
|
344
|
+
* A PROTECTED branch's key is NOT reserved, at either end. No path in the
|
|
345
|
+
* system can delete a protected branch (`git.deleteBranch` throws
|
|
345
346
|
* `ProtectedBranchError` before any lock, `retireMergedSourceBranch`
|
|
346
347
|
* returns early on one), so the key would exclude nothing. It would only
|
|
347
|
-
* cost: nearly every request targets the protected default branch,
|
|
348
|
-
*
|
|
349
|
-
*
|
|
350
|
-
*
|
|
348
|
+
* cost: nearly every request targets the protected default branch, and
|
|
349
|
+
* every draft refreshes FROM it (`updateBranch`, the merge tool's sync),
|
|
350
|
+
* so one shared key there would serialize every open on the hottest path for the
|
|
351
|
+
* duration of a fetch + merge + push + diff. A protected SOURCE takes no
|
|
352
|
+
* key for the same reason: `openChangeRequest` refuses protected sources
|
|
353
|
+
* before locking, and a draft refreshing from the default branch is
|
|
354
|
+
* exactly a protected source.
|
|
351
355
|
*/
|
|
352
356
|
runOnBranchPair(source, target, fn) {
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
357
|
+
// A PROTECTED branch's key is not reserved at EITHER end (see above):
|
|
358
|
+
// nothing can delete one, so the key would exclude nothing and only
|
|
359
|
+
// serialize every draft that refreshes from the default branch behind
|
|
360
|
+
// every other. The same branch at both ends (a draft syncing its own
|
|
361
|
+
// shared state) takes its one key.
|
|
362
|
+
const keys = [...new Set([source, target].filter((b) => !this.kb.isProtectedBranch(b)).map((b) => `branch:${b}`))];
|
|
356
363
|
return this.branchLifecycle.runAll(keys, fn);
|
|
357
364
|
}
|
|
358
365
|
/**
|
|
@@ -798,7 +805,16 @@ export class WorkflowService {
|
|
|
798
805
|
* only code that can observe it under the workspace mutex.
|
|
799
806
|
*/
|
|
800
807
|
async pullWorkspace(workspaceId, opts = {}) {
|
|
801
|
-
|
|
808
|
+
return this.pullWorkspaceVia(() => this.git.pull(workspaceId, opts), workspaceId);
|
|
809
|
+
}
|
|
810
|
+
/**
|
|
811
|
+
* `pullWorkspace` over the pull it is given: the workspace's own
|
|
812
|
+
* (`git.pull`, which takes the workspace's reservation) or one a caller
|
|
813
|
+
* already holding that reservation hands in (a merge's publish). The
|
|
814
|
+
* announcement is the same either way.
|
|
815
|
+
*/
|
|
816
|
+
async pullWorkspaceVia(pull, workspaceId) {
|
|
817
|
+
const { treeChanged } = await pull();
|
|
802
818
|
const defaultBranch = this.kb.defaultBranch;
|
|
803
819
|
if (treeChanged && branchForWorkspaceId(workspaceId) === defaultBranch) {
|
|
804
820
|
this.events?.emit({ kind: 'fs-tree-changed', workspaceId, branch: defaultBranch });
|
|
@@ -1063,8 +1079,8 @@ export class WorkflowService {
|
|
|
1063
1079
|
listChangesForFile(workspaceId, path, limit) {
|
|
1064
1080
|
return this.git.logForFile(workspaceId, path, limit);
|
|
1065
1081
|
}
|
|
1066
|
-
compareFile(workspaceId, path, fromBranch, toBranch) {
|
|
1067
|
-
return this.git.diffFileBetweenBranches(workspaceId, path, fromBranch, toBranch);
|
|
1082
|
+
compareFile(workspaceId, path, fromBranch, toBranch, authorize) {
|
|
1083
|
+
return this.git.diffFileBetweenBranches(workspaceId, path, fromBranch, toBranch, authorize);
|
|
1068
1084
|
}
|
|
1069
1085
|
fileAtForkPoint(workspaceId, baseBranch, sha, path) {
|
|
1070
1086
|
return this.git.readFileAtForkPoint(workspaceId, baseBranch, sha, path);
|
|
@@ -1574,7 +1590,20 @@ export class WorkflowService {
|
|
|
1574
1590
|
async pushWithRecovery(workspaceId, branch, targetPath, user, opts) {
|
|
1575
1591
|
// No explicit `undefined` third argument when there are no options: the
|
|
1576
1592
|
// call shape stays the bare `push(workspaceId, user)` every caller made.
|
|
1577
|
-
|
|
1593
|
+
return this.pushWithRecoveryVia({
|
|
1594
|
+
push: () => (opts ? this.git.push(workspaceId, user, opts) : this.git.push(workspaceId, user)),
|
|
1595
|
+
pull: () => this.pullWorkspace(workspaceId, { preserveMerges: true }),
|
|
1596
|
+
}, workspaceId, branch, targetPath, user);
|
|
1597
|
+
}
|
|
1598
|
+
/**
|
|
1599
|
+
* `pushWithRecovery`'s policy over the two moves it is made of — the push,
|
|
1600
|
+
* and the pull-rebase that keeps a stranded merge a merge — so that a
|
|
1601
|
+
* caller already holding the workspace's reservation (a merge publishing
|
|
1602
|
+
* itself, `updateBranch`) runs the SAME policy over its own unlocked moves,
|
|
1603
|
+
* and a push the host refuses answers one way there too.
|
|
1604
|
+
*/
|
|
1605
|
+
async pushWithRecoveryVia(ops, workspaceId, branch, targetPath, user) {
|
|
1606
|
+
const push = ops.push;
|
|
1578
1607
|
try {
|
|
1579
1608
|
await push();
|
|
1580
1609
|
this.noteGitSyncOk(workspaceId, branch);
|
|
@@ -1596,7 +1625,7 @@ export class WorkflowService {
|
|
|
1596
1625
|
// with whatever push of the branch comes next — a save, a share —
|
|
1597
1626
|
// and a plain rebase would flatten it into cherry-picks. With no
|
|
1598
1627
|
// merge among the local commits the two spellings replay the same.
|
|
1599
|
-
await
|
|
1628
|
+
await ops.pull();
|
|
1600
1629
|
try {
|
|
1601
1630
|
await push();
|
|
1602
1631
|
}
|
|
@@ -3496,8 +3525,8 @@ export class WorkflowService {
|
|
|
3496
3525
|
// while they are held: a bare read would let a person open the request
|
|
3497
3526
|
// mid-merge and the agent would land its content anyway, which is the one
|
|
3498
3527
|
// thing this tool exists to prevent. `runOnBranchPair` drops a protected
|
|
3499
|
-
//
|
|
3500
|
-
// request from this source takes it too.
|
|
3528
|
+
// branch's key at either end; a draft source's key is held, and every
|
|
3529
|
+
// competing request from this source takes it too.
|
|
3501
3530
|
return this.runOnBranchPair(sourceBranch, targetBranch, async () => {
|
|
3502
3531
|
// A person merges a change request. Only the request's OWN direction is
|
|
3503
3532
|
// blocked: the target merged into the source is how a draft under review
|
|
@@ -3922,5 +3951,123 @@ export class WorkflowService {
|
|
|
3922
3951
|
return '';
|
|
3923
3952
|
return formatAffectedOwnersBlock(paths, resolved);
|
|
3924
3953
|
}
|
|
3954
|
+
async updateBranch(workspaceId, user, opts = {}) {
|
|
3955
|
+
const branch = branchForWorkspaceId(workspaceId);
|
|
3956
|
+
const from = opts.from ?? branch;
|
|
3957
|
+
// A resolution writes on the draft: it is somebody's to gate, and a
|
|
3958
|
+
// caller that brings none gates nothing (closed) — the type says so for
|
|
3959
|
+
// a compiled caller, this for any other.
|
|
3960
|
+
if ((opts.resolutions?.length ?? 0) > 0 && typeof opts.validateWrite !== 'function') {
|
|
3961
|
+
throw new Error('updateBranch: resolutions need a `validateWrite` to be asked before the merge');
|
|
3962
|
+
}
|
|
3963
|
+
if (this.kb.isProtectedBranch(branch)) {
|
|
3964
|
+
throw new WorkflowDomainError(`"${branch}" is protected: it is changed through change requests, never merged into directly.`, 403, { kind: 'protected-update-target', branch });
|
|
3965
|
+
}
|
|
3966
|
+
// The one merge a person makes: a change request's. An update that would
|
|
3967
|
+
// land an OPEN request from `from` into this draft is that merge by
|
|
3968
|
+
// another name, and `mergeBranch`'s gate refuses it here too — asked once
|
|
3969
|
+
// before waiting, and again under the locks below, where the answer
|
|
3970
|
+
// counts. (The other direction, the request's target into its source, is
|
|
3971
|
+
// how a draft under review stays current, and is never blocked.)
|
|
3972
|
+
if (from !== branch) {
|
|
3973
|
+
const openBefore = await this.openRequestBlockingMerge(from, branch);
|
|
3974
|
+
if (openBefore !== null)
|
|
3975
|
+
throw new OpenChangeRequestBlocksMergeError(from, branch, openBefore);
|
|
3976
|
+
}
|
|
3977
|
+
// The merge takes what the REMOTE holds of `from`, and the push publishes
|
|
3978
|
+
// the branch: writes still being committed on either would be missed by
|
|
3979
|
+
// the first or swept up by the second. Waited for, as `mergeBranch` waits
|
|
3980
|
+
// — outside the lifecycle locks, which must not be held for seconds of
|
|
3981
|
+
// waiting on a queue.
|
|
3982
|
+
for (const b of from === branch ? [branch] : [from, branch]) {
|
|
3983
|
+
const settled = await this.awaitPendingCommits(b);
|
|
3984
|
+
if (!settled.ok) {
|
|
3985
|
+
const message = settled.needsAttention
|
|
3986
|
+
? `A commit queued on "${b}" failed and needs a person: ${settled.needsAttention} ` +
|
|
3987
|
+
'Nothing was merged. Tell the user; update again once it has been resolved.'
|
|
3988
|
+
: `${settled.pending} write(s) on "${b}" are still being committed. Nothing was merged. Retry shortly.`;
|
|
3989
|
+
return {
|
|
3990
|
+
kind: 'pending-commits',
|
|
3991
|
+
branch: b,
|
|
3992
|
+
pending: settled.pending,
|
|
3993
|
+
...(settled.needsAttention ? { needsAttention: settled.needsAttention } : {}),
|
|
3994
|
+
message,
|
|
3995
|
+
};
|
|
3996
|
+
}
|
|
3997
|
+
}
|
|
3998
|
+
// Under the lifecycle lock of both branches, as `mergeBranch` and
|
|
3999
|
+
// `openChangeRequest` run: the "no open request" answer is only sound
|
|
4000
|
+
// while nobody can open one mid-merge, and a branch being merged into is
|
|
4001
|
+
// not one to delete or recreate meanwhile. A protected `from` takes no
|
|
4002
|
+
// key, and the branch's own shared state (from = branch) takes the one.
|
|
4003
|
+
return this.runOnBranchPair(from, branch, async () => {
|
|
4004
|
+
if (from !== branch) {
|
|
4005
|
+
const open = await this.openRequestBlockingMerge(from, branch);
|
|
4006
|
+
if (open !== null)
|
|
4007
|
+
throw new OpenChangeRequestBlocksMergeError(from, branch, open);
|
|
4008
|
+
}
|
|
4009
|
+
if (!(await this.git.remoteBranchExists(workspaceId, from))) {
|
|
4010
|
+
throw new WorkflowValidationError(`No branch named "${from}" on the shared repository.`);
|
|
4011
|
+
}
|
|
4012
|
+
const resolutions = opts.resolutions ?? [];
|
|
4013
|
+
const subject = from === branch ? `Merge the shared ${branch} into the local one` : `Merge ${from} into ${branch}`;
|
|
4014
|
+
const body = (from === branch
|
|
4015
|
+
? `The branch had moved on the shared repository while this workspace held commits of its own; both are kept.`
|
|
4016
|
+
: `Brings the latest ${from} into ${branch}.`) +
|
|
4017
|
+
(resolutions.length > 0
|
|
4018
|
+
? ` Conflicts resolved by ${user.name} <${user.email}> in: ${resolutions.map((r) => r.path).join(', ')}.`
|
|
4019
|
+
: '');
|
|
4020
|
+
// Published inside the merge's own reservation: nothing lands between
|
|
4021
|
+
// the merge commit and its push, and the push is EVERY workflow push's
|
|
4022
|
+
// `pushWithRecovery`, run over the merge's own unlocked moves — a
|
|
4023
|
+
// rejection replays the merge (kept as a merge) onto the moved branch
|
|
4024
|
+
// and pushes again; a replay that conflicts or a host that refuses
|
|
4025
|
+
// answers as every refused share answers, the banner noted and the
|
|
4026
|
+
// typed hand-off thrown, naming the branch (no single file is at issue)
|
|
4027
|
+
// and never a conflicted path. The merge commit stays local for the
|
|
4028
|
+
// next push of the branch to carry.
|
|
4029
|
+
return this.git.mergeIntoBranch(workspaceId, branch, from, user, {
|
|
4030
|
+
subject,
|
|
4031
|
+
body,
|
|
4032
|
+
resolutions,
|
|
4033
|
+
publish: (ops) => this.pushWithRecoveryVia({ push: ops.push, pull: () => this.pullWorkspaceVia(ops.pullRebase, workspaceId) }, workspaceId, branch, branch, user),
|
|
4034
|
+
// A resolution writes a file on the draft from what both branches
|
|
4035
|
+
// hold of it. It is authorized inside the merge's reservation, after
|
|
4036
|
+
// the fetch and before the merge touches the checkout — the draft's
|
|
4037
|
+
// tree clean at its own commit, so the rules read from it are the
|
|
4038
|
+
// draft's, never a rule file mid-merge: the caller must read the
|
|
4039
|
+
// file at the draft's commit and at the source commit the merge is
|
|
4040
|
+
// of, and may change it on the draft by the one rule every draft
|
|
4041
|
+
// write takes — the change gate `acquireLock` asks, not a write
|
|
4042
|
+
// permission a draft never requires.
|
|
4043
|
+
authorizeResolutions: async ({ ours, theirs }) => {
|
|
4044
|
+
for (const r of resolutions) {
|
|
4045
|
+
const rel = r.path.startsWith(`${this.kbDirName}/`) ? r.path.slice(this.kbDirName.length + 1) : r.path;
|
|
4046
|
+
// At the two COMMITS the merge is of — pinned by the merge itself,
|
|
4047
|
+
// never at a ref a concurrent fetch can move between this verdict
|
|
4048
|
+
// and the merge — by the one rule the tool shows a conflict by.
|
|
4049
|
+
if (!(await readableAtCommits(this.accessControl, workspaceId, user.email, rel, [ours, theirs]))) {
|
|
4050
|
+
throw new ReadOnBranchDeniedError(rel, from);
|
|
4051
|
+
}
|
|
4052
|
+
// The gate takes the WORKSPACE path, as `acquireLock` hands it
|
|
4053
|
+
// one: a repository-relative path reads as outside the knowledge
|
|
4054
|
+
// base there, which the gate waves through.
|
|
4055
|
+
await this.changeGate.assertMayChange(workspaceId, user.email, `${this.kbDirName}/${rel}`, 'file');
|
|
4056
|
+
// Content the resolution brings takes the caller's pre-disk write
|
|
4057
|
+
// gate, as its file writes do before a byte lands; a side kept
|
|
4058
|
+
// whole is a commit's own bytes and brings none.
|
|
4059
|
+
if (r.content !== undefined && opts.validateWrite)
|
|
4060
|
+
await opts.validateWrite(`${this.kbDirName}/${rel}`, r.content);
|
|
4061
|
+
}
|
|
4062
|
+
},
|
|
4063
|
+
});
|
|
4064
|
+
});
|
|
4065
|
+
}
|
|
4066
|
+
fileAtBranch(workspaceId, path, branch, authorize) {
|
|
4067
|
+
return this.git.readFileAtBranch(workspaceId, path, branch, authorize);
|
|
4068
|
+
}
|
|
4069
|
+
changedPathsBetweenBranches(workspaceId, base, branch) {
|
|
4070
|
+
return this.git.changedPathsForPr(workspaceId, base, branch);
|
|
4071
|
+
}
|
|
3925
4072
|
}
|
|
3926
4073
|
//# sourceMappingURL=workflow.service.js.map
|