diff --git a/.changeset/fn-8083-gitlab-tracking-hydration.md b/.changeset/fn-8083-gitlab-tracking-hydration.md new file mode 100644 index 0000000000..0943b9ba2f --- /dev/null +++ b/.changeset/fn-8083-gitlab-tracking-hydration.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": patch +--- + +summary: Preserve GitLab import tracking metadata when tasks are read or restored. +category: fix +dev: GitLab tracking now has archive/restore parity with the shared TaskStore mapping. diff --git a/packages/core/src/__tests__/postgres/store-gitlab-tracking-hydration.pg.test.ts b/packages/core/src/__tests__/postgres/store-gitlab-tracking-hydration.pg.test.ts index 21b078d40b..8014f3104d 100644 --- a/packages/core/src/__tests__/postgres/store-gitlab-tracking-hydration.pg.test.ts +++ b/packages/core/src/__tests__/postgres/store-gitlab-tracking-hydration.pg.test.ts @@ -7,10 +7,10 @@ import { } from "../../__test-utils__/pg-test-harness.js"; /* -FNXC:GitLabTracking 2026-07-16-05:38: -GitLab tracking must round-trip through the shared TaskStore registry on every -live and soft-deleted read surface. A persisted empty object means not filed -for analytics and must remain distinct from absent tracking, which hydrates as undefined. +FNXC:GitLabTracking 2026-07-16-13:00: +GitLab import provenance must round-trip through every shared TaskStore read path +and archive/restore. Missing tracking remains undefined, while a populated item +must retain its full payload so imports can be reconciled after restoration. */ pgDescribe("TaskStore GitLab tracking hydration (PostgreSQL)", () => { const h: SharedPgTaskStoreHarness = createSharedPgTaskStoreTestHarness({ @@ -37,30 +37,26 @@ pgDescribe("TaskStore GitLab tracking hydration (PostgreSQL)", () => { createdAt: "2026-07-16T00:00:00.000Z", }; - it("round-trips GitLab tracking across live and soft-deleted task reads", async () => { + it("round-trips GitLab tracking across every live read and archive restore", async () => { const store = h.store(); - const tracked = await store.createTask({ - description: "Tracked GitLab task", - sourceIssue: { - provider: "gitlab", - repository: "acme/app", - externalIssueId: "42", - issueNumber: 2, - url: item.url, - }, - gitlabTracking: { item }, - }); - const empty = await store.createTask({ description: "Empty GitLab tracking", gitlabTracking: {} }); - const absent = await store.createTask({ description: "Absent GitLab tracking" }); - - expect((await store.getTask(tracked.id))?.gitlabTracking?.item).toEqual(item); - expect((await store.getTask(tracked.id))?.sourceIssue).toEqual({ - provider: "gitlab", + const modifiedSince = "1970-01-01T00:00:00.000Z"; + const sourceIssue = { + provider: "gitlab" as const, repository: "acme/app", externalIssueId: "42", issueNumber: 2, url: item.url, + }; + const tracked = await store.createTask({ + description: "Tracked GitLab task", + sourceIssue, + gitlabTracking: { item }, }); + const absent = await store.createTask({ description: "Absent GitLab tracking" }); + + expect((await store.getTask(tracked.id))?.gitlabTracking?.item).toEqual(item); + expect((await store.getTask(tracked.id))?.sourceIssue).toEqual(sourceIssue); + expect((await store.getTask(absent.id))?.gitlabTracking).toBeUndefined(); for (const slim of [false, true]) { const listed = await store.listTasks({ slim }); @@ -69,17 +65,19 @@ pgDescribe("TaskStore GitLab tracking hydration (PostgreSQL)", () => { if (slim) expect(listedTask?.log).toEqual([]); } - expect((await store.getTask(empty.id))?.gitlabTracking).toEqual({}); - expect((await store.listTasks({ slim: false })).find((task) => task.id === empty.id)?.gitlabTracking).toEqual({}); - expect((await store.getTask(absent.id))?.gitlabTracking).toBeUndefined(); + const searched = await store.searchTasks("Tracked GitLab task", { slim: true }); + expect(searched.find((task) => task.id === tracked.id)?.gitlabTracking?.item).toEqual(item); - await store.deleteTask(tracked.id); + const modified = await store.listTasksModifiedSince(modifiedSince); + expect(modified.tasks.find((task) => task.id === tracked.id)?.gitlabTracking?.item).toEqual(item); - for (const slim of [false, true]) { - const deleted = await store.listTasks({ includeDeleted: true, slim }); - expect(deleted.find((task) => task.id === tracked.id)?.gitlabTracking?.item).toEqual(item); - } - expect((await store.getTask(tracked.id, { includeDeleted: true }))?.gitlabTracking?.item).toEqual(item); - expect((await store.listTasksForGitlabTrackingReconcile()).tasks.find((task) => task.id === tracked.id)?.gitlabTracking?.item).toEqual(item); + await store.moveTask(tracked.id, "todo"); + await store.moveTask(tracked.id, "in-progress"); + await store.moveTask(tracked.id, "done"); + await store.archiveTask(tracked.id, false); + const restored = await store.unarchiveTask(tracked.id); + + expect(restored.gitlabTracking?.item).toEqual(item); + expect((await store.getTask(tracked.id))?.gitlabTracking?.item).toEqual(item); }); }); diff --git a/packages/core/src/task-store/archive-lifecycle-2.ts b/packages/core/src/task-store/archive-lifecycle-2.ts index f96fe66ea9..cb2c7d3284 100644 --- a/packages/core/src/task-store/archive-lifecycle-2.ts +++ b/packages/core/src/task-store/archive-lifecycle-2.ts @@ -49,6 +49,12 @@ export async function taskToArchiveEntryImpl(store: TaskStore, task: Task, archi prInfos: task.prInfos, issueInfo: task.issueInfo, githubTracking: task.githubTracking, + /* + FNXC:GitLabTracking 2026-07-16-13:00: + Archiving must retain GitLab provenance just as live TaskStore persistence does; + restored imports need their original GitLab tracking item for reconciliation. + */ + gitlabTracking: task.gitlabTracking, sourceIssue: task.sourceIssue, attachments: task.attachments, comments: task.comments, @@ -410,6 +416,7 @@ export async function restoreFromArchiveImpl(store: TaskStore, entry: import(".. review: entry.review, issueInfo: entry.issueInfo, githubTracking: entry.githubTracking, + gitlabTracking: entry.gitlabTracking, sourceIssue: entry.sourceIssue, attachments: entry.attachments, log: [...entry.log, { timestamp: new Date().toISOString(), action: "Task restored from archive" }], diff --git a/packages/core/src/task-store/serialization.ts b/packages/core/src/task-store/serialization.ts index ea7bc1aaaa..a25cdc3abd 100644 --- a/packages/core/src/task-store/serialization.ts +++ b/packages/core/src/task-store/serialization.ts @@ -333,6 +333,7 @@ export function archiveEntryToTask( prInfos: slim ? undefined : entry.prInfos, issueInfo: slim ? undefined : entry.issueInfo, githubTracking: entry.githubTracking, + gitlabTracking: entry.gitlabTracking, sourceIssue: slim ? undefined : entry.sourceIssue, attachments: slim ? undefined : entry.attachments, comments: entry.comments,