feat(FN-5129): complete Step 2 — enforce lineage gate in deleteTask
Fusion-Task-Id: FN-5129 Fusion-Task-Lineage: 77519a37-0552-4e8b-8061-ce84012de978
This commit is contained in:
committed by
gsxdsm
parent
911125f1c9
commit
5d9669ad35
@@ -5718,7 +5718,11 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
|
|||||||
*/
|
*/
|
||||||
async deleteTask(
|
async deleteTask(
|
||||||
id: string,
|
id: string,
|
||||||
options?: { removeDependencyReferences?: boolean; githubIssueAction?: GithubIssueAction },
|
options?: {
|
||||||
|
removeDependencyReferences?: boolean;
|
||||||
|
removeLineageReferences?: boolean;
|
||||||
|
githubIssueAction?: GithubIssueAction;
|
||||||
|
},
|
||||||
): Promise<Task> {
|
): Promise<Task> {
|
||||||
return this.withTaskLock(id, async () => {
|
return this.withTaskLock(id, async () => {
|
||||||
// Flush buffered agent logs inside the lock so no new appends for this
|
// Flush buffered agent logs inside the lock so no new appends for this
|
||||||
@@ -5741,6 +5745,12 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
|
|||||||
throw new TaskHasDependentsError(id, dependentIds);
|
throw new TaskHasDependentsError(id, dependentIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FN-5127: lineage gate must execute after idempotent short-circuit.
|
||||||
|
const lineageChildIds = this.findLiveLineageChildren(id);
|
||||||
|
if (lineageChildIds.length > 0 && !options?.removeLineageReferences) {
|
||||||
|
throw new TaskHasLineageChildrenError(id, lineageChildIds);
|
||||||
|
}
|
||||||
|
|
||||||
// Clean up the task's branch before deleting from DB
|
// Clean up the task's branch before deleting from DB
|
||||||
const cleanedBranches = await this.cleanupBranchForTask(task);
|
const cleanedBranches = await this.cleanupBranchForTask(task);
|
||||||
if (cleanedBranches.length > 0) {
|
if (cleanedBranches.length > 0) {
|
||||||
@@ -5752,8 +5762,10 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let rewrittenDependents: Task[] = [];
|
let rewrittenDependents: Task[] = [];
|
||||||
|
let rewrittenLineageChildren: Task[] = [];
|
||||||
this.db.transaction(() => {
|
this.db.transaction(() => {
|
||||||
rewrittenDependents = this.rewriteDependentsForRemoval(id, dependentIds);
|
rewrittenDependents = this.rewriteDependentsForRemoval(id, dependentIds);
|
||||||
|
rewrittenLineageChildren = this.rewriteLineageChildrenForRemoval(id, lineageChildIds);
|
||||||
const deletedAt = new Date().toISOString();
|
const deletedAt = new Date().toISOString();
|
||||||
this.db.prepare("UPDATE tasks SET deletedAt = ?, updatedAt = ? WHERE id = ?").run(deletedAt, deletedAt, id);
|
this.db.prepare("UPDATE tasks SET deletedAt = ?, updatedAt = ? WHERE id = ?").run(deletedAt, deletedAt, id);
|
||||||
this.clearLinkedAgentTaskIds(id, deletedAt);
|
this.clearLinkedAgentTaskIds(id, deletedAt);
|
||||||
@@ -5766,6 +5778,9 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
|
|||||||
for (const dependentTask of rewrittenDependents) {
|
for (const dependentTask of rewrittenDependents) {
|
||||||
this.emit("task:updated", dependentTask);
|
this.emit("task:updated", dependentTask);
|
||||||
}
|
}
|
||||||
|
for (const lineageChild of rewrittenLineageChildren) {
|
||||||
|
this.emit("task:updated", lineageChild);
|
||||||
|
}
|
||||||
|
|
||||||
const linkedFeature = this.missionStore?.getFeatureByTaskId(id);
|
const linkedFeature = this.missionStore?.getFeatureByTaskId(id);
|
||||||
if (linkedFeature) {
|
if (linkedFeature) {
|
||||||
@@ -5815,6 +5830,29 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
|
|||||||
return rewrittenDependents;
|
return rewrittenDependents;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private rewriteLineageChildrenForRemoval(parentId: string, childIds: string[]): Task[] {
|
||||||
|
const rewrittenChildren: Task[] = [];
|
||||||
|
|
||||||
|
for (const childId of childIds) {
|
||||||
|
const childTask = this.readTaskFromDb(childId);
|
||||||
|
if (!childTask || childTask.sourceParentTaskId !== parentId) continue;
|
||||||
|
|
||||||
|
const updatedChild: Task = {
|
||||||
|
...childTask,
|
||||||
|
sourceParentTaskId: undefined,
|
||||||
|
updatedAt: new Date().toISOString(),
|
||||||
|
};
|
||||||
|
|
||||||
|
this.db.prepare("UPDATE tasks SET sourceParentTaskId = NULL, updatedAt = ? WHERE id = ?").run(updatedChild.updatedAt, updatedChild.id);
|
||||||
|
if (this.isWatching) {
|
||||||
|
this.taskCache.set(updatedChild.id, updatedChild);
|
||||||
|
}
|
||||||
|
rewrittenChildren.push(updatedChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
return rewrittenChildren;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clear `agent.taskId` links that point at a task which has transitioned out
|
* Clear `agent.taskId` links that point at a task which has transitioned out
|
||||||
* of active work. This keeps heartbeat scheduling aligned with live task
|
* of active work. This keeps heartbeat scheduling aligned with live task
|
||||||
|
|||||||
Reference in New Issue
Block a user