FN-6245: keep default auto-merge tasks on live settings
Keep tasks without explicit auto-merge overrides tied to the live global setting instead of freezing it at review entry. - Stop in-review entry effects from copying the project autoMerge setting onto tasks. - Preserve explicit per-task auto-merge overrides while allowing default tasks to follow later global toggles. - Add regression coverage for move-to-review behavior and live auto-merge resolution. - Document the default auto-merge behavior and add a patch changeset. Files changed: .changeset/fn-6245-automerge-toggle.md | 5 ++ docs/settings-reference.md | 2 +- .../__tests__/move-task-characterization.test.ts | 48 ++++++++++++++--- packages/core/src/__tests__/store-movement.test.ts | 63 +++++++++++++++------- packages/core/src/__tests__/task-merge.test.ts | 12 ++++- packages/core/src/default-workflow-hooks.ts | 28 ++++++---- packages/core/src/store.ts | 13 ++--- 7 files changed, 121 insertions(+), 50 deletions(-) Fusion-Task-Id: FN-6245 Fusion-Task-Lineage: 1f349f0c-e513-4d82-b77a-9d0c353c163c
This commit is contained in:
5
.changeset/fn-6245-automerge-toggle.md
Normal file
5
.changeset/fn-6245-automerge-toggle.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"@runfusion/fusion": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Stop review entry from freezing the global auto-merge setting onto tasks. Tasks without an explicit per-task auto-merge override now continue to follow the live global setting, so toggling global auto-merge off stops newly-entered non-override in-review tasks from being auto-merge processed.
|
||||||
@@ -280,7 +280,7 @@ Defaults from `DEFAULT_PROJECT_SETTINGS`; key scope from `PROJECT_SETTINGS_KEYS`
|
|||||||
| `groupOverlappingFiles` | `boolean` | `true` | Serialize execution when file scopes overlap. |
|
| `groupOverlappingFiles` | `boolean` | `true` | Serialize execution when file scopes overlap. |
|
||||||
| `pluginTrustPolicy` | `"off" | "warn" | "enforce"` | `"warn"` | Plugin provenance enforcement mode: `off` records verification metadata only, `warn` blocks only `invalid` signatures, `enforce` allows only `verified-trusted` or `trusted-local`. |
|
| `pluginTrustPolicy` | `"off" | "warn" | "enforce"` | `"warn"` | Plugin provenance enforcement mode: `off` records verification metadata only, `warn` blocks only `invalid` signatures, `enforce` allows only `verified-trusted` or `trusted-local`. |
|
||||||
| `overlapIgnorePaths` | `string[]` | `[]` | Optional project-relative file or directory paths to exclude from overlap blocking (for example `docs` or `generated/openapi.json`). Entries are trimmed, deduplicated, and must not be absolute or contain `..` traversal. |
|
| `overlapIgnorePaths` | `string[]` | `[]` | Optional project-relative file or directory paths to exclude from overlap blocking (for example `docs` or `generated/openapi.json`). Entries are trimmed, deduplicated, and must not be absolute or contain `..` traversal. |
|
||||||
| `autoMerge` | `boolean` | `true` | Auto-finalize tasks from `in-review`. Tasks can override this per-task (including at create time in New Task modal via **Auto-merge** = Default/Enabled/Disabled). For grouped branch flows, per-task `autoMerge` governs member→group-integration landing while group `autoMerge` governs group→default-branch promotion eligibility. |
|
| `autoMerge` | `boolean` | `true` | Auto-finalize tasks from `in-review`. Tasks can override this per-task (including at create time in New Task modal via **Auto-merge** = Default/Enabled/Disabled); tasks left at **Default** keep following the live global setting and do not snapshot it when entering review. For grouped branch flows, per-task `autoMerge` governs member→group-integration landing while group `autoMerge` governs group→default-branch promotion eligibility. |
|
||||||
| `mergeRequestContractShadowEnabled` | `boolean` | `false` | Phase-1 FN-5741 write-only shadow flag (project/global setting). When enabled, executor/self-healing/merger persist merge-request records and `completion_handoff_accepted` markers for observation only; legacy mergeQueue + lifecycle remains authoritative. |
|
| `mergeRequestContractShadowEnabled` | `boolean` | `false` | Phase-1 FN-5741 write-only shadow flag (project/global setting). When enabled, executor/self-healing/merger persist merge-request records and `completion_handoff_accepted` markers for observation only; legacy mergeQueue + lifecycle remains authoritative. |
|
||||||
| `mergeStrategy` | `"direct" \| "pull-request"` | `"direct"` | Completion mode (local direct merge vs PR-first). |
|
| `mergeStrategy` | `"direct" \| "pull-request"` | `"direct"` | Completion mode (local direct merge vs PR-first). |
|
||||||
| `directMergeCommitStrategy` | `"auto" \| "always-squash" \| "always-rebase"` | `"always-squash"` | Direct-merge commit routing mode. `always-squash` (default) forces the legacy squash path. `auto` keeps the legacy squash path for branches with zero or one substantive commit, but switches multi-substantive direct merges to a history-preserving rebase-and-merge/cherry-pick path so commit boundaries, subjects, and `Fusion-Task-Id` trailers survive on `main`. `always-rebase` always preserves per-commit history. Only applies when `mergeStrategy="direct"`. |
|
| `directMergeCommitStrategy` | `"auto" \| "always-squash" \| "always-rebase"` | `"always-squash"` | Direct-merge commit routing mode. `always-squash` (default) forces the legacy squash path. `auto` keeps the legacy squash path for branches with zero or one substantive commit, but switches multi-substantive direct merges to a history-preserving rebase-and-merge/cherry-pick path so commit boundaries, subjects, and `Fusion-Task-Id` trailers survive on `main`. `always-rebase` always preserves per-commit history. Only applies when `mergeStrategy="direct"`. |
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
// - merge-blocker on in-review → done (user source)
|
// - merge-blocker on in-review → done (user source)
|
||||||
// - userPaused set only for user-source in-progress → todo
|
// - userPaused set only for user-source in-progress → todo
|
||||||
// - reopen field/step resets on in-review/done → todo|triage
|
// - reopen field/step resets on in-review/done → todo|triage
|
||||||
// - autoMerge stamping on → in-review
|
// - autoMerge live-global inheritance on → in-review
|
||||||
// - timing fields (cumulativeActiveMs / executionStartedAt) on in-progress
|
// - timing fields (cumulativeActiveMs / executionStartedAt) on in-progress
|
||||||
//
|
//
|
||||||
// It runs GREEN against the unmodified store first, then runs forever against
|
// It runs GREEN against the unmodified store first, then runs forever against
|
||||||
@@ -17,11 +17,13 @@
|
|||||||
// Any divergence between the two flag states is a U4 parity FAILURE.
|
// Any divergence between the two flag states is a U4 parity FAILURE.
|
||||||
|
|
||||||
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
||||||
|
import { allowsAutoMergeProcessing, resolveEffectiveAutoMerge } from "../task-merge.js";
|
||||||
import { VALID_TRANSITIONS } from "../types.js";
|
import { VALID_TRANSITIONS } from "../types.js";
|
||||||
import type { Column, Task } from "../types.js";
|
import type { Column, Task } from "../types.js";
|
||||||
import { createTaskStoreTestHarness } from "./store-test-helpers.js";
|
import { createTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||||
|
|
||||||
const ALL_COLUMNS: Column[] = ["triage", "todo", "in-progress", "in-review", "done", "archived"];
|
const ALL_COLUMNS: Column[] = ["triage", "todo", "in-progress", "in-review", "done", "archived"];
|
||||||
|
const MOVE_SOURCES = ["user", "engine", "scheduler"] as const;
|
||||||
|
|
||||||
// Flag states the characterization runs against. OFF is the legacy path; ON is
|
// Flag states the characterization runs against. OFF is the legacy path; ON is
|
||||||
// the workflow-resolved path. The default workflow MUST reproduce identical
|
// the workflow-resolved path. The default workflow MUST reproduce identical
|
||||||
@@ -83,7 +85,7 @@ for (const flag of flagStates) {
|
|||||||
describe("transition allow/reject matrix (every from×to×moveSource)", () => {
|
describe("transition allow/reject matrix (every from×to×moveSource)", () => {
|
||||||
for (const from of ALL_COLUMNS) {
|
for (const from of ALL_COLUMNS) {
|
||||||
for (const to of ALL_COLUMNS) {
|
for (const to of ALL_COLUMNS) {
|
||||||
for (const moveSource of ["user", "engine"] as const) {
|
for (const moveSource of MOVE_SOURCES) {
|
||||||
const allowed = from === to || VALID_TRANSITIONS[from].includes(to);
|
const allowed = from === to || VALID_TRANSITIONS[from].includes(to);
|
||||||
const label = `${from} → ${to} [${moveSource}] should ${allowed ? "ALLOW" : "REJECT"}`;
|
const label = `${from} → ${to} [${moveSource}] should ${allowed ? "ALLOW" : "REJECT"}`;
|
||||||
it(label, async () => {
|
it(label, async () => {
|
||||||
@@ -170,15 +172,45 @@ for (const flag of flagStates) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("autoMerge stamping (→ in-review)", () => {
|
describe("autoMerge live-global inheritance (→ in-review)", () => {
|
||||||
it("stamps autoMerge from settings when undefined", async () => {
|
for (const moveSource of MOVE_SOURCES) {
|
||||||
await store.updateSettings({ autoMerge: true });
|
it(`leaves undefined autoMerge to follow live settings for ${moveSource}-source moves`, async () => {
|
||||||
const task = await seedInColumn("in-progress");
|
await store.updateSettings({ autoMerge: true });
|
||||||
const result = await store.moveTask(task.id, "in-review", {
|
const task = await seedInColumn("in-progress");
|
||||||
moveSource: "user",
|
const result = await store.moveTask(task.id, "in-review", {
|
||||||
|
moveSource,
|
||||||
|
allowDirectInReviewMove: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.autoMerge).toBeUndefined();
|
||||||
|
expect(allowsAutoMergeProcessing(result, { autoMerge: false })).toBe(false);
|
||||||
|
expect(allowsAutoMergeProcessing(result, { autoMerge: true })).toBe(true);
|
||||||
|
expect(resolveEffectiveAutoMerge(result, { autoMerge: false })).toBe(false);
|
||||||
|
expect(resolveEffectiveAutoMerge(result, { autoMerge: true })).toBe(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
it("preserves explicit task autoMerge overrides", async () => {
|
||||||
|
await store.updateSettings({ autoMerge: false });
|
||||||
|
const explicitTrue = await seedInColumn("in-progress");
|
||||||
|
await store.updateTask(explicitTrue.id, { autoMerge: true });
|
||||||
|
const trueResult = await store.moveTask(explicitTrue.id, "in-review", {
|
||||||
|
moveSource: "engine",
|
||||||
allowDirectInReviewMove: true,
|
allowDirectInReviewMove: true,
|
||||||
});
|
});
|
||||||
expect(result.autoMerge).toBe(true);
|
expect(trueResult.autoMerge).toBe(true);
|
||||||
|
expect(allowsAutoMergeProcessing(trueResult, { autoMerge: false })).toBe(true);
|
||||||
|
|
||||||
|
await store.updateSettings({ autoMerge: true });
|
||||||
|
const explicitFalse = await seedInColumn("in-progress");
|
||||||
|
await store.updateTask(explicitFalse.id, { autoMerge: false });
|
||||||
|
const falseResult = await store.moveTask(explicitFalse.id, "in-review", {
|
||||||
|
moveSource: "scheduler",
|
||||||
|
allowDirectInReviewMove: true,
|
||||||
|
});
|
||||||
|
expect(falseResult.autoMerge).toBe(false);
|
||||||
|
expect(resolveEffectiveAutoMerge(falseResult, { autoMerge: true })).toBe(false);
|
||||||
|
expect(resolveEffectiveAutoMerge(falseResult, { autoMerge: false })).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import * as projectMemory from "../project-memory.js";
|
|||||||
import { AgentStore } from "../agent-store.js";
|
import { AgentStore } from "../agent-store.js";
|
||||||
import { CentralDatabase } from "../central-db.js";
|
import { CentralDatabase } from "../central-db.js";
|
||||||
import { TaskStore, TaskHasDependentsError } from "../store.js";
|
import { TaskStore, TaskHasDependentsError } from "../store.js";
|
||||||
|
import { allowsAutoMergeProcessing, resolveEffectiveAutoMerge } from "../task-merge.js";
|
||||||
import { buildResearchDocumentKey, type Task } from "../types.js";
|
import { buildResearchDocumentKey, type Task } from "../types.js";
|
||||||
import { createTaskStoreTestHarness, makeTmpDir } from "./store-test-helpers.js";
|
import { createTaskStoreTestHarness, makeTmpDir } from "./store-test-helpers.js";
|
||||||
|
|
||||||
@@ -60,36 +61,58 @@ describe("TaskStore", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
describe("moveTask — autoMerge snapshot on in-review", () => {
|
describe("moveTask — autoMerge follows live settings on in-review", () => {
|
||||||
it("snapshots global autoMerge=true when task override is undefined", async () => {
|
async function createInProgressTask(description: string): Promise<Task> {
|
||||||
|
const task = await store.createTask({ description });
|
||||||
|
await store.moveTask(task.id, "todo");
|
||||||
|
return store.moveTask(task.id, "in-progress");
|
||||||
|
}
|
||||||
|
|
||||||
|
it("does not snapshot global autoMerge=true when task override is undefined", async () => {
|
||||||
await store.updateSettings({ autoMerge: true });
|
await store.updateSettings({ autoMerge: true });
|
||||||
const task = await store.createTask({ description: "snapshot true" });
|
const task = await createInProgressTask("no snapshot true");
|
||||||
await store.moveTask(task.id, "todo");
|
|
||||||
await store.moveTask(task.id, "in-progress");
|
|
||||||
|
|
||||||
const moved = await store.moveTask(task.id, "in-review");
|
const moved = await store.moveTask(task.id, "in-review");
|
||||||
expect(moved.autoMerge).toBe(true);
|
|
||||||
|
expect(moved.autoMerge).toBeUndefined();
|
||||||
|
expect(allowsAutoMergeProcessing(moved, { autoMerge: true })).toBe(true);
|
||||||
|
expect(allowsAutoMergeProcessing(moved, { autoMerge: false })).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("snapshots global autoMerge=false when task override is undefined", async () => {
|
it("does not snapshot global autoMerge=false when task override is undefined", async () => {
|
||||||
await store.updateSettings({ autoMerge: false });
|
await store.updateSettings({ autoMerge: false });
|
||||||
const task = await store.createTask({ description: "snapshot false" });
|
const task = await createInProgressTask("no snapshot false");
|
||||||
await store.moveTask(task.id, "todo");
|
|
||||||
await store.moveTask(task.id, "in-progress");
|
|
||||||
|
|
||||||
const moved = await store.moveTask(task.id, "in-review");
|
const moved = await store.moveTask(task.id, "in-review");
|
||||||
expect(moved.autoMerge).toBe(false);
|
|
||||||
|
expect(moved.autoMerge).toBeUndefined();
|
||||||
|
expect(resolveEffectiveAutoMerge(moved, { autoMerge: false })).toBe(false);
|
||||||
|
expect(resolveEffectiveAutoMerge(moved, { autoMerge: true })).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("preserves explicit task autoMerge override when entering in-review", async () => {
|
it("tracks live global toggles for undefined while preserving explicit overrides", async () => {
|
||||||
await store.updateSettings({ autoMerge: false });
|
await store.updateSettings({ autoMerge: true });
|
||||||
const task = await store.createTask({ description: "explicit override" });
|
const inherited = await createInProgressTask("inherits live global");
|
||||||
await store.updateTask(task.id, { autoMerge: true });
|
const inheritedMoved = await store.moveTask(inherited.id, "in-review");
|
||||||
await store.moveTask(task.id, "todo");
|
|
||||||
await store.moveTask(task.id, "in-progress");
|
|
||||||
|
|
||||||
const moved = await store.moveTask(task.id, "in-review");
|
expect(inheritedMoved.autoMerge).toBeUndefined();
|
||||||
expect(moved.autoMerge).toBe(true);
|
expect(allowsAutoMergeProcessing(inheritedMoved, { autoMerge: false })).toBe(false);
|
||||||
|
expect(allowsAutoMergeProcessing(inheritedMoved, { autoMerge: true })).toBe(true);
|
||||||
|
|
||||||
|
const explicitTrue = await createInProgressTask("explicit true override");
|
||||||
|
await store.updateTask(explicitTrue.id, { autoMerge: true });
|
||||||
|
const explicitTrueMoved = await store.moveTask(explicitTrue.id, "in-review");
|
||||||
|
expect(explicitTrueMoved.autoMerge).toBe(true);
|
||||||
|
expect(allowsAutoMergeProcessing(explicitTrueMoved, { autoMerge: false })).toBe(true);
|
||||||
|
expect(resolveEffectiveAutoMerge(explicitTrueMoved, { autoMerge: false })).toBe(true);
|
||||||
|
|
||||||
|
const explicitFalse = await createInProgressTask("explicit false override");
|
||||||
|
await store.updateTask(explicitFalse.id, { autoMerge: false });
|
||||||
|
const explicitFalseMoved = await store.moveTask(explicitFalse.id, "in-review");
|
||||||
|
expect(explicitFalseMoved.autoMerge).toBe(false);
|
||||||
|
expect(allowsAutoMergeProcessing(explicitFalseMoved, { autoMerge: false })).toBe(false);
|
||||||
|
expect(resolveEffectiveAutoMerge(explicitFalseMoved, { autoMerge: false })).toBe(false);
|
||||||
|
expect(resolveEffectiveAutoMerge(explicitFalseMoved, { autoMerge: true })).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -45,6 +45,13 @@ describe("resolveEffectiveAutoMerge", () => {
|
|||||||
it("falls back to global false when task value is undefined", () => {
|
it("falls back to global false when task value is undefined", () => {
|
||||||
expect(resolveEffectiveAutoMerge({ autoMerge: undefined }, { autoMerge: false })).toBe(false);
|
expect(resolveEffectiveAutoMerge({ autoMerge: undefined }, { autoMerge: false })).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("tracks live global toggles while task value remains undefined", () => {
|
||||||
|
const task = { autoMerge: undefined };
|
||||||
|
expect(resolveEffectiveAutoMerge(task, { autoMerge: true })).toBe(true);
|
||||||
|
expect(resolveEffectiveAutoMerge(task, { autoMerge: false })).toBe(false);
|
||||||
|
expect(resolveEffectiveAutoMerge(task, { autoMerge: true })).toBe(true);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("allowsAutoMergeProcessing", () => {
|
describe("allowsAutoMergeProcessing", () => {
|
||||||
@@ -53,7 +60,10 @@ describe("allowsAutoMergeProcessing", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("blocks tasks without an explicit override when the global setting is off", () => {
|
it("blocks tasks without an explicit override when the global setting is off", () => {
|
||||||
expect(allowsAutoMergeProcessing({ autoMerge: undefined }, { autoMerge: false })).toBe(false);
|
const task = { autoMerge: undefined };
|
||||||
|
expect(allowsAutoMergeProcessing(task, { autoMerge: true })).toBe(true);
|
||||||
|
expect(allowsAutoMergeProcessing(task, { autoMerge: false })).toBe(false);
|
||||||
|
expect(allowsAutoMergeProcessing(task, { autoMerge: true })).toBe(true);
|
||||||
expect(allowsAutoMergeProcessing({ autoMerge: false }, { autoMerge: false })).toBe(false);
|
expect(allowsAutoMergeProcessing({ autoMerge: false }, { autoMerge: false })).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,10 @@
|
|||||||
* Default-workflow trait hook implementations (U4).
|
* Default-workflow trait hook implementations (U4).
|
||||||
*
|
*
|
||||||
* The legacy per-column side effects of `moveTaskInternal` — timing /
|
* The legacy per-column side effects of `moveTaskInternal` — timing /
|
||||||
* `cumulativeActiveMs` accounting, reopen field/step resets, autoMerge stamping
|
* `cumulativeActiveMs` accounting, reopen field/step resets, in-review
|
||||||
* + merge-queue enqueue, and abort-on-exit (hard-cancel incl. `userPaused` only
|
* auto-merge handoff preparation + merge-queue enqueue, and abort-on-exit
|
||||||
* for user-source moves) — become the default workflow's trait hook
|
* (hard-cancel incl. `userPaused` only for user-source moves) — become the
|
||||||
|
* default workflow's trait hook
|
||||||
* implementations, registered through U2's DI seam (`registerTraitHookImpl`).
|
* implementations, registered through U2's DI seam (`registerTraitHookImpl`).
|
||||||
*
|
*
|
||||||
* IMPORTANT (per U4): this is the FLAG-ON path. The legacy inline code in
|
* IMPORTANT (per U4): this is the FLAG-ON path. The legacy inline code in
|
||||||
@@ -73,7 +74,11 @@ export interface DefaultWorkflowMoveContext {
|
|||||||
/** True when guards + abort-on-exit are bypassed (engine/recovery, KTD-9). */
|
/** True when guards + abort-on-exit are bypassed (engine/recovery, KTD-9). */
|
||||||
bypassGuards: boolean;
|
bypassGuards: boolean;
|
||||||
movedAt: string;
|
movedAt: string;
|
||||||
/** Settings snapshot for autoMerge stamping (only read when entering review). */
|
/**
|
||||||
|
* Settings snapshot available to move effects that need it. Review entry must
|
||||||
|
* not copy global `autoMerge` onto the task; an undefined task value follows
|
||||||
|
* the live global setting at processing time.
|
||||||
|
*/
|
||||||
settings: Pick<Settings, "autoMerge"> | undefined;
|
settings: Pick<Settings, "autoMerge"> | undefined;
|
||||||
/** Move options that influence reopen/timing semantics. */
|
/** Move options that influence reopen/timing semantics. */
|
||||||
options: {
|
options: {
|
||||||
@@ -165,15 +170,16 @@ export function applyResetOnEntryEffects(ctx: DefaultWorkflowMoveContext): void
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** `merge` trait onEnter (in-review): autoMerge stamping + scheduler-state
|
/** `merge` trait onEnter (in-review): scheduler-state clearing while
|
||||||
* clearing. The queue enqueue itself is in-txn and store-owned (handoff path);
|
* preserving explicit per-task autoMerge overrides. The queue enqueue itself is
|
||||||
* the field effects mirror the legacy in-review block. */
|
* in-txn and store-owned (handoff path); the field effects mirror the legacy
|
||||||
|
* in-review block. Keep this flag-ON path in sync with the flag-OFF inline
|
||||||
|
* block in store.ts. */
|
||||||
export function applyInReviewEnterEffects(ctx: DefaultWorkflowMoveContext): void {
|
export function applyInReviewEnterEffects(ctx: DefaultWorkflowMoveContext): void {
|
||||||
const { task, toColumn, settings } = ctx;
|
const { task, toColumn } = ctx;
|
||||||
if (toColumn !== "in-review") return;
|
if (toColumn !== "in-review") return;
|
||||||
if (task.autoMerge === undefined && settings) {
|
// Do not snapshot the global autoMerge setting here. Undefined means "follow
|
||||||
task.autoMerge = settings.autoMerge;
|
// the live global setting"; only an explicit task value should stay sticky.
|
||||||
}
|
|
||||||
task.recoveryRetryCount = undefined;
|
task.recoveryRetryCount = undefined;
|
||||||
task.nextRecoveryAt = undefined;
|
task.nextRecoveryAt = undefined;
|
||||||
if (task.status === "queued") {
|
if (task.status === "queued") {
|
||||||
|
|||||||
@@ -6832,11 +6832,6 @@ ${TASK_UPSERT_SQL_ASSIGNMENTS}
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const settingsForInReview =
|
|
||||||
toColumn === "in-review" && task.autoMerge === undefined
|
|
||||||
? await this.getSettingsFast()
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
const movedAt = internal.now ?? new Date().toISOString();
|
const movedAt = internal.now ?? new Date().toISOString();
|
||||||
task.column = toColumn;
|
task.column = toColumn;
|
||||||
task.columnMovedAt = movedAt;
|
task.columnMovedAt = movedAt;
|
||||||
@@ -6854,7 +6849,7 @@ ${TASK_UPSERT_SQL_ASSIGNMENTS}
|
|||||||
moveSource,
|
moveSource,
|
||||||
bypassGuards,
|
bypassGuards,
|
||||||
movedAt,
|
movedAt,
|
||||||
settings: settingsForInReview,
|
settings: undefined,
|
||||||
options: {
|
options: {
|
||||||
preserveStatus: options?.preserveStatus,
|
preserveStatus: options?.preserveStatus,
|
||||||
preserveResumeState: options?.preserveResumeState,
|
preserveResumeState: options?.preserveResumeState,
|
||||||
@@ -6958,9 +6953,9 @@ ${TASK_UPSERT_SQL_ASSIGNMENTS}
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (toColumn === "in-review") {
|
if (toColumn === "in-review") {
|
||||||
if (task.autoMerge === undefined && settingsForInReview) {
|
// Keep this flag-OFF inline path in sync with applyInReviewEnterEffects.
|
||||||
task.autoMerge = settingsForInReview.autoMerge;
|
// Do not snapshot global autoMerge: undefined follows the live setting,
|
||||||
}
|
// while explicit per-task true/false overrides remain sticky.
|
||||||
task.recoveryRetryCount = undefined;
|
task.recoveryRetryCount = undefined;
|
||||||
task.nextRecoveryAt = undefined;
|
task.nextRecoveryAt = undefined;
|
||||||
// Clear scheduler-side dispatch state: `queued`, `blockedBy`, and
|
// Clear scheduler-side dispatch state: `queued`, `blockedBy`, and
|
||||||
|
|||||||
Reference in New Issue
Block a user