fix(core): restore PostgreSQL lifecycle activity logging (#2344)

## Summary

PostgreSQL-backed projects now record automatic TaskStore lifecycle
activity, including ordinary task creation and movement. Backend
initialization previously returned before registering the lifecycle
listeners, while features that called `recordActivity()` directly could
make the log appear only partially affected. The fix wires the
backend-agnostic listeners without changing SQLite initialization
behavior.

## Validation

- PostgreSQL activity parity test verifies all six listener surfaces and
persisted `task:created` and `task:moved` entries.
- `pnpm lint`
- `pnpm --filter @fusion/core typecheck`
- `pnpm check:changesets --strict`
- `pnpm verify:fast`


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Restored automatic task lifecycle entries in PostgreSQL activity logs
by ensuring task lifecycle activity listeners are wired during
PostgreSQL backend initialization.
* Activity logs now reliably include task creation and movement events
(with transition details) immediately after backend startup.
* **Tests**
* Added a PostgreSQL activity-log parity test to verify listeners are
connected and that activity logs reflect `task:created` and `task:moved`
as expected.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
gsxdsm
2026-07-19 12:34:55 -07:00
committed by GitHub
parent 48b3656bde
commit 048ae909fd
3 changed files with 51 additions and 6 deletions

View File

@@ -0,0 +1,7 @@
---
"@runfusion/fusion": patch
---
summary: Restore automatic task lifecycle entries in PostgreSQL activity logs.
category: fix
dev: Registers TaskStore activity listeners during PostgreSQL backend initialization.

View File

@@ -23,6 +23,44 @@ pgDescribe("activity log parity (PostgreSQL)", () => {
afterEach(h.afterEach);
afterAll(h.afterAll);
it("records TaskStore lifecycle events after backend initialization", async () => {
const store = h.store();
expect(store.activityListenersWired).toBe(true);
for (const event of [
"task:created",
"task:moved",
"task:merged",
"task:updated",
"task:deleted",
"settings:updated",
] as const) {
expect(store.listenerCount(event), `${event} activity listener`).toBeGreaterThan(0);
}
const task = await store.createTask({
title: "Backend lifecycle activity",
description: "Verify PostgreSQL lifecycle activity logging",
});
await store.moveTask(task.id, "todo", { moveSource: "user" });
await vi.waitFor(async () => {
const entries = await store.getActivityLog({ limit: 10 });
expect(entries).toEqual(expect.arrayContaining([
expect.objectContaining({
type: "task:created",
taskId: task.id,
taskTitle: task.title,
}),
expect.objectContaining({
type: "task:moved",
taskId: task.id,
metadata: { from: "triage", to: "todo" },
}),
]));
});
});
it("keeps failed writes best-effort so audit storage cannot break product operations", async () => {
const layer = h.layer();
const insert = vi.spyOn(layer.db, "insert").mockImplementation(() => {

View File

@@ -47,12 +47,9 @@ export async function initImpl(store: TaskStore): Promise<void> {
// In backend mode (an AsyncDataLayer was injected), TaskStore skips ALL
// SQLite construction and the SQLite-specific startup reconciliations
// (corruption guard, legacy file migration, agent-log file migration,
// schema-version re-init, orphaned task-dir reconcile, activity-log
// listener wiring that reads from SQLite, etc.). The PostgreSQL schema
// baseline is applied by the startup factory before constructing the
// store, and the async equivalents of these reconciliations are wired by
// the runtime-*-async features. init() in backend mode performs only the
// backend-agnostic setup (mkdir, trait-hook registration) above and returns.
// schema-version re-init, orphaned task-dir reconcile, etc.). The PostgreSQL
// schema baseline is applied by the startup factory before constructing the
// store. Backend-safe startup work is performed explicitly in the branch below.
//
// When the async layer is ABSENT, the entire block below runs exactly as
// before — byte-identical to the pre-migration SQLite path.
@@ -81,6 +78,9 @@ export async function initImpl(store: TaskStore): Promise<void> {
async store API (listTasks/updateTask), so it is PG-safe.
*/
await adoptLegacyTaskRowsOnOpen(store);
// Lifecycle listeners are backend-agnostic: recordActivity() routes their
// best-effort writes through the injected PostgreSQL data layer.
store.setupActivityLogListeners();
return;
}