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:
7
.changeset/fix-postgres-activity-lifecycle.md
Normal file
7
.changeset/fix-postgres-activity-lifecycle.md
Normal 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.
|
||||||
@@ -23,6 +23,44 @@ pgDescribe("activity log parity (PostgreSQL)", () => {
|
|||||||
afterEach(h.afterEach);
|
afterEach(h.afterEach);
|
||||||
afterAll(h.afterAll);
|
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 () => {
|
it("keeps failed writes best-effort so audit storage cannot break product operations", async () => {
|
||||||
const layer = h.layer();
|
const layer = h.layer();
|
||||||
const insert = vi.spyOn(layer.db, "insert").mockImplementation(() => {
|
const insert = vi.spyOn(layer.db, "insert").mockImplementation(() => {
|
||||||
|
|||||||
@@ -47,12 +47,9 @@ export async function initImpl(store: TaskStore): Promise<void> {
|
|||||||
// In backend mode (an AsyncDataLayer was injected), TaskStore skips ALL
|
// In backend mode (an AsyncDataLayer was injected), TaskStore skips ALL
|
||||||
// SQLite construction and the SQLite-specific startup reconciliations
|
// SQLite construction and the SQLite-specific startup reconciliations
|
||||||
// (corruption guard, legacy file migration, agent-log file migration,
|
// (corruption guard, legacy file migration, agent-log file migration,
|
||||||
// schema-version re-init, orphaned task-dir reconcile, activity-log
|
// schema-version re-init, orphaned task-dir reconcile, etc.). The PostgreSQL
|
||||||
// listener wiring that reads from SQLite, etc.). The PostgreSQL schema
|
// schema baseline is applied by the startup factory before constructing the
|
||||||
// baseline is applied by the startup factory before constructing the
|
// store. Backend-safe startup work is performed explicitly in the branch below.
|
||||||
// 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.
|
|
||||||
//
|
//
|
||||||
// When the async layer is ABSENT, the entire block below runs exactly as
|
// When the async layer is ABSENT, the entire block below runs exactly as
|
||||||
// before — byte-identical to the pre-migration SQLite path.
|
// 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.
|
async store API (listTasks/updateTask), so it is PG-safe.
|
||||||
*/
|
*/
|
||||||
await adoptLegacyTaskRowsOnOpen(store);
|
await adoptLegacyTaskRowsOnOpen(store);
|
||||||
|
// Lifecycle listeners are backend-agnostic: recordActivity() routes their
|
||||||
|
// best-effort writes through the injected PostgreSQL data layer.
|
||||||
|
store.setupActivityLogListeners();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user