feat(FN-5105): complete Step 1 — add deletedAt schema plumbing
Fusion-Task-Id: FN-5105 Fusion-Task-Lineage: a876d5eb-d828-415e-b6f0-07bbc8713417
This commit is contained in:
committed by
gsxdsm
parent
d683cbda33
commit
a12047b603
@@ -689,6 +689,37 @@ describe("schema migration", () => {
|
||||
db.close();
|
||||
});
|
||||
|
||||
it("adds deletedAt column + index when migrating from schema version 86", () => {
|
||||
const db = new Database(fusionDir);
|
||||
db.exec("CREATE TABLE IF NOT EXISTS __meta (key TEXT PRIMARY KEY, value TEXT)");
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS tasks (
|
||||
id TEXT PRIMARY KEY,
|
||||
description TEXT NOT NULL,
|
||||
"column" TEXT NOT NULL,
|
||||
createdAt TEXT NOT NULL,
|
||||
updatedAt TEXT NOT NULL
|
||||
)
|
||||
`);
|
||||
db.exec("INSERT INTO __meta (key, value) VALUES ('schemaVersion', '86')");
|
||||
db.exec("INSERT INTO __meta (key, value) VALUES ('lastModified', '1000')");
|
||||
db.exec("INSERT INTO tasks (id, description, \"column\", createdAt, updatedAt) VALUES ('FN-legacy', 'legacy', 'todo', '2025-01-01T00:00:00.000Z', '2025-01-01T00:00:00.000Z')");
|
||||
|
||||
db.init();
|
||||
|
||||
const columns = db.prepare("PRAGMA table_info(tasks)").all() as Array<{ name: string }>;
|
||||
expect(columns.map((column) => column.name)).toContain("deletedAt");
|
||||
|
||||
const indexes = db.prepare("PRAGMA index_list(tasks)").all() as Array<{ name: string }>;
|
||||
expect(indexes.some((index) => index.name === "idx_tasks_deletedAt")).toBe(true);
|
||||
|
||||
const row = db.prepare("SELECT deletedAt FROM tasks WHERE id = 'FN-legacy'").get() as { deletedAt: string | null };
|
||||
expect(row.deletedAt).toBeNull();
|
||||
expect(db.getSchemaVersion()).toBe(87);
|
||||
|
||||
db.close();
|
||||
});
|
||||
|
||||
it("adds workflow_steps.gateMode and backfills legacy rows by mode", () => {
|
||||
const db = new Database(fusionDir);
|
||||
db.exec("CREATE TABLE IF NOT EXISTS __meta (key TEXT PRIMARY KEY, value TEXT)");
|
||||
@@ -717,7 +748,7 @@ describe("schema migration", () => {
|
||||
{ id: "WS-001", mode: "prompt", gateMode: "advisory" },
|
||||
{ id: "WS-002", mode: "script", gateMode: "advisory" },
|
||||
]);
|
||||
expect(db.getSchemaVersion()).toBe(86);
|
||||
expect(db.getSchemaVersion()).toBe(87);
|
||||
|
||||
db.close();
|
||||
});
|
||||
@@ -767,7 +798,7 @@ describe("schema migration", () => {
|
||||
reviewerContextRetryCount: 0,
|
||||
reviewerFallbackRetryCount: 0,
|
||||
});
|
||||
expect(db.getSchemaVersion()).toBe(86);
|
||||
expect(db.getSchemaVersion()).toBe(87);
|
||||
|
||||
db.close();
|
||||
});
|
||||
@@ -796,7 +827,7 @@ describe("schema migration", () => {
|
||||
|
||||
const columns = db.prepare("PRAGMA table_info(milestones)").all() as Array<{ name: string }>;
|
||||
expect(columns.map((column) => column.name)).toContain("acceptanceCriteria");
|
||||
expect(db.getSchemaVersion()).toBe(86);
|
||||
expect(db.getSchemaVersion()).toBe(87);
|
||||
|
||||
db.close();
|
||||
});
|
||||
@@ -831,7 +862,7 @@ describe("schema migration", () => {
|
||||
{ id: "WS-002", mode: "script", enabled: 1, gateMode: "advisory" },
|
||||
{ id: "WS-003", mode: "prompt", enabled: 0, gateMode: "advisory" },
|
||||
]);
|
||||
expect(db.getSchemaVersion()).toBe(86);
|
||||
expect(db.getSchemaVersion()).toBe(87);
|
||||
|
||||
db.close();
|
||||
});
|
||||
|
||||
@@ -120,7 +120,7 @@ export function probeFts5(db: DatabaseSync): boolean {
|
||||
|
||||
// ── Schema Definition ────────────────────────────────────────────────
|
||||
|
||||
const SCHEMA_VERSION = 86;
|
||||
const SCHEMA_VERSION = 87;
|
||||
|
||||
function normalizeTaskComments(
|
||||
steeringComments: SteeringComment[] | undefined,
|
||||
@@ -283,7 +283,8 @@ CREATE TABLE IF NOT EXISTS tasks (
|
||||
checkoutNodeId TEXT,
|
||||
checkoutRunId TEXT,
|
||||
checkoutLeaseRenewedAt TEXT,
|
||||
checkoutLeaseEpoch INTEGER DEFAULT 0
|
||||
checkoutLeaseEpoch INTEGER DEFAULT 0,
|
||||
deletedAt TEXT
|
||||
);
|
||||
|
||||
-- Config table (single row with project settings)
|
||||
@@ -3414,6 +3415,13 @@ export class Database {
|
||||
});
|
||||
}
|
||||
|
||||
if (version < 87) {
|
||||
this.applyMigration(87, () => {
|
||||
this.addColumnIfMissing("tasks", "deletedAt", "TEXT");
|
||||
this.db.exec("CREATE INDEX IF NOT EXISTS idx_tasks_deletedAt ON tasks(deletedAt)");
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -168,6 +168,7 @@ interface TaskRow {
|
||||
checkoutRunId: string | null;
|
||||
checkoutLeaseRenewedAt: string | null;
|
||||
checkoutLeaseEpoch: number | null;
|
||||
deletedAt: string | null;
|
||||
}
|
||||
|
||||
/** Database row shape for the task_documents table. */
|
||||
@@ -1278,6 +1279,7 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
|
||||
checkoutRunId: row.checkoutRunId || undefined,
|
||||
checkoutLeaseRenewedAt: row.checkoutLeaseRenewedAt || undefined,
|
||||
checkoutLeaseEpoch: row.checkoutLeaseEpoch ?? undefined,
|
||||
deletedAt: row.deletedAt ?? undefined,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1519,7 +1521,7 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
|
||||
"breakIntoSubtasks", "noCommitsExpected", "enabledWorkflowSteps", "modifiedFiles",
|
||||
"missionId", "sliceId", "scopeOverride", "scopeOverrideReason", "assignedAgentId", "pausedByAgentId", "assigneeUserId", "nodeId", "effectiveNodeId", "effectiveNodeSource",
|
||||
"sourceType", "sourceAgentId", "sourceRunId", "sourceSessionId", "sourceMessageId", "sourceParentTaskId", "sourceMetadata",
|
||||
"checkedOutBy", "checkedOutAt", "checkoutNodeId", "checkoutRunId", "checkoutLeaseRenewedAt", "checkoutLeaseEpoch",
|
||||
"checkedOutBy", "checkedOutAt", "checkoutNodeId", "checkoutRunId", "checkoutLeaseRenewedAt", "checkoutLeaseEpoch", "deletedAt",
|
||||
// `log` is fetched in slim mode so the server can aggregate
|
||||
// `timedExecutionMs` from `[timing] … in <N>ms` entries before
|
||||
// returning. The log itself is stripped from the response —
|
||||
@@ -1568,7 +1570,7 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
|
||||
"breakIntoSubtasks", "noCommitsExpected", "enabledWorkflowSteps", "modifiedFiles",
|
||||
"missionId", "sliceId", "scopeOverride", "scopeOverrideReason", "assignedAgentId", "pausedByAgentId", "assigneeUserId", "nodeId", "effectiveNodeId", "effectiveNodeSource",
|
||||
"sourceType", "sourceAgentId", "sourceRunId", "sourceSessionId", "sourceMessageId", "sourceParentTaskId", "sourceMetadata",
|
||||
"checkedOutBy", "checkedOutAt", "checkoutNodeId", "checkoutRunId", "checkoutLeaseRenewedAt", "checkoutLeaseEpoch",
|
||||
"checkedOutBy", "checkedOutAt", "checkoutNodeId", "checkoutRunId", "checkoutLeaseRenewedAt", "checkoutLeaseEpoch", "deletedAt",
|
||||
];
|
||||
|
||||
const limitedLog = `
|
||||
@@ -1703,6 +1705,7 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
|
||||
task.checkoutRunId ?? null,
|
||||
task.checkoutLeaseRenewedAt ?? null,
|
||||
task.checkoutLeaseEpoch ?? 0,
|
||||
task.deletedAt ?? null,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1725,7 +1728,7 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
|
||||
dependencies, steps, log, attachments, steeringComments,
|
||||
comments, review, reviewState, workflowStepResults, prInfo, prInfos, issueInfo, githubTracking,
|
||||
sourceIssueProvider, sourceIssueRepository, sourceIssueExternalIssueId, sourceIssueNumber, sourceIssueUrl,
|
||||
mergeDetails, breakIntoSubtasks, noCommitsExpected, enabledWorkflowSteps, modifiedFiles, missionId, sliceId, scopeOverride, scopeOverrideReason, assignedAgentId, pausedByAgentId, assigneeUserId, nodeId, effectiveNodeId, effectiveNodeSource, sourceType, sourceAgentId, sourceRunId, sourceSessionId, sourceMessageId, sourceParentTaskId, sourceMetadata, checkedOutBy, checkedOutAt, checkoutNodeId, checkoutRunId, checkoutLeaseRenewedAt, checkoutLeaseEpoch
|
||||
mergeDetails, breakIntoSubtasks, noCommitsExpected, enabledWorkflowSteps, modifiedFiles, missionId, sliceId, scopeOverride, scopeOverrideReason, assignedAgentId, pausedByAgentId, assigneeUserId, nodeId, effectiveNodeId, effectiveNodeSource, sourceType, sourceAgentId, sourceRunId, sourceSessionId, sourceMessageId, sourceParentTaskId, sourceMetadata, checkedOutBy, checkedOutAt, checkoutNodeId, checkoutRunId, checkoutLeaseRenewedAt, checkoutLeaseEpoch, deletedAt
|
||||
) VALUES (${placeholders})
|
||||
`).run(...values);
|
||||
this.db.bumpLastModified();
|
||||
@@ -1752,7 +1755,7 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
|
||||
dependencies, steps, log, attachments, steeringComments,
|
||||
comments, review, reviewState, workflowStepResults, prInfo, prInfos, issueInfo, githubTracking,
|
||||
sourceIssueProvider, sourceIssueRepository, sourceIssueExternalIssueId, sourceIssueNumber, sourceIssueUrl,
|
||||
mergeDetails, breakIntoSubtasks, noCommitsExpected, enabledWorkflowSteps, modifiedFiles, missionId, sliceId, scopeOverride, scopeOverrideReason, assignedAgentId, pausedByAgentId, assigneeUserId, nodeId, effectiveNodeId, effectiveNodeSource, sourceType, sourceAgentId, sourceRunId, sourceSessionId, sourceMessageId, sourceParentTaskId, sourceMetadata, checkedOutBy, checkedOutAt, checkoutNodeId, checkoutRunId, checkoutLeaseRenewedAt, checkoutLeaseEpoch
|
||||
mergeDetails, breakIntoSubtasks, noCommitsExpected, enabledWorkflowSteps, modifiedFiles, missionId, sliceId, scopeOverride, scopeOverrideReason, assignedAgentId, pausedByAgentId, assigneeUserId, nodeId, effectiveNodeId, effectiveNodeSource, sourceType, sourceAgentId, sourceRunId, sourceSessionId, sourceMessageId, sourceParentTaskId, sourceMetadata, checkedOutBy, checkedOutAt, checkoutNodeId, checkoutRunId, checkoutLeaseRenewedAt, checkoutLeaseEpoch, deletedAt
|
||||
) VALUES (${placeholders})
|
||||
ON CONFLICT(id) DO UPDATE SET
|
||||
lineageId = excluded.lineageId,
|
||||
@@ -1861,7 +1864,8 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
|
||||
checkoutNodeId = excluded.checkoutNodeId,
|
||||
checkoutRunId = excluded.checkoutRunId,
|
||||
checkoutLeaseRenewedAt = excluded.checkoutLeaseRenewedAt,
|
||||
checkoutLeaseEpoch = excluded.checkoutLeaseEpoch
|
||||
checkoutLeaseEpoch = excluded.checkoutLeaseEpoch,
|
||||
deletedAt = excluded.deletedAt
|
||||
`).run(...this.getTaskPersistValues(task));
|
||||
this.db.bumpLastModified();
|
||||
}
|
||||
|
||||
@@ -3595,6 +3595,8 @@ export interface ArchivedTaskEntry {
|
||||
executionStartedAt?: string;
|
||||
/** First-time completion anchor; may be cleared on reopen. */
|
||||
executionCompletedAt?: string;
|
||||
/** ISO timestamp set when the task is soft-deleted from active views. */
|
||||
deletedAt?: string;
|
||||
/** Timestamp when the task was archived to the log */
|
||||
archivedAt: string;
|
||||
/** Optional: model preset and override fields for executor and validator */
|
||||
|
||||
Reference in New Issue
Block a user