Files
fusion/packages/core/src/task-store/task-commit-associations.ts
gsxdsm 5ae6332563 refactor: collapse dead SQLite dual-path code; keep migration-only readers (#2454)
# Remove dead SQLite dual-path code; keep migration-only readers

## Summary
PostgreSQL cutover left hundreds of production dual-path branches
(`backendMode ? PG : SQLite/store.db`) whose SQLite arms only hit
throwing `Database`/`ArchiveDatabase`/`CentralDatabase` stubs. This
change mechanically collapses those unreachable arms so production
authority is AsyncDataLayer/PostgreSQL only, while preserving the six
authorized read-only migration/recovery `DatabaseSync` seams.

## Dual-path mass removed
| Metric | Before | After |
|---|---|---|
| `if (…backendMode)` (non-test) | ~328 | ~70 |
| `store.db` / `this.db` refs in core (non-test) | ~570+ | ~375 (mostly
pure legacy MissionStore/eval/insight SQLite classes + thin getters) |
| Net diff | — | **~6.7k lines removed** across 41 files |

Remaining `backendMode` checks are intentional (incomplete-PG sync
safe-defaults, settings-sync disabled-on-PG, symbol-lock PG-only gates,
“requires PostgreSQL” config versioning throws), not live SQLite
authority.

## Subsystems cleaned
- **Core TaskStore / task-store/***: collapsed if/else and early-return
dual-path across reads, moves, lifecycle, mutations, workflow, archive,
branch/PR, artifacts, comments, audit, project ops, etc. `initImpl` is
PostgreSQL-only (SQLite startup tail deleted).
- **Satellite stores**: automation, agent, routine, plugin, secrets,
approval-request, central-core dual-path arms collapsed.
- **Plugins**: reports async methods, compound-engineering pipeline +
session stores, CLI Printing Press store — SQLite fallbacks removed; PG
required.
- **Engine**: no functional dual-path change beyond whitespace
(settings-sync / peer-exchange PG-disabled behavior kept).

## Six migration-only readers retained (allowlist unchanged)
1. `packages/core/src/postgres/sqlite-migrator.ts`
2. `packages/core/src/project-identity.ts`
3. `packages/core/src/sqlite-validation.ts`
4. `packages/core/src/postgres/startup-factory.ts`
5. `packages/cli/src/commands/db.ts`
6. `scripts/lib/start-local-project.mjs`

Plus low-level `sqlite-adapter` and migrator/startup-import tests.
Inventory ratchet still requires exactly these six `new DatabaseSync(`
production sites, all `readOnly: true`.

## Not treated as SQLite
- `.fusion/project.json`, `task.json`, `agent-log.jsonl` file storage
- AsyncDataLayer / Drizzle PG paths
- Incomplete-PG sync safe-default stubs (still return empty/false/null
under backend without consulting SQLite)

## Verification
- `sqlite-production-reader-inventory.test.ts` — 15/15 pass
- `incomplete-pg-ports.pg.test.ts` — 6/6 pass
- Targeted PG tests (create-task, move, handoff, runtime-persistence,
agent, mission, insight, central-core) — green
- `tsc --noEmit` for `@fusion/core`, `@fusion/engine`,
`@fusion/dashboard` — green
- `scripts/check-no-getdatabase.mjs` — clean

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

* **Improvements**
* Improved end-to-end consistency by making PostgreSQL/async persistence
the standard across core task/workflow, automation, agents, plugins,
routines, secrets, approvals, central operations, and session storage.
* Unified scheduling, settings, configuration revision writes,
run/workflow selection, queues/leases/transitions, and audit/lifecycle
updates around consistent async transaction behavior.
* **Bug Fixes**
* Fixed edge cases for archived/deleted reads, unarchive/recovery flows,
not-found handling, and task/artifact/document/log/comment operations,
including more reliable emissions and hydration across search/list and
lifecycle operations.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-26 23:28:42 -07:00

79 lines
3.1 KiB
TypeScript

/**
* Task commit-association persistence (domain module).
*
* FNXC:StoreModularization 2026-06-25-00:00:
* Extracted from the monolithic packages/core/src/store.ts as a pure
* behavior-preserving refactor. Each function receives the TaskStore
* instance as its first parameter and performs byte-identical work.
*
* FNXC:CodeOrganization 2026-07-15-00:00:
* Renamed from remaining-ops-9 to a domain name; no behavior change.
*/
import { TaskStore } from "../store.js";
import { normalizeTaskCommitAssociation } from "../task-lineage.js";
import { TaskCommitAssociationRow } from "./row-types.js";
import { TaskCommitAssociation } from "../types.js";
import { and, desc, eq, inArray } from "drizzle-orm";
import * as schema from "../postgres/schema/index.js";
export async function getTaskCommitAssociationsByLineageIdImpl(store: TaskStore, lineageId: string): Promise<TaskCommitAssociation[]> {
/*
FNXC:PostgresCutover 2026-07-04:
Backend-mode read of task_commit_associations by lineage id via async
Drizzle. Mirrors the SQLite ORDER BY authoredAt DESC, createdAt DESC. The
Drizzle select returns camelCase columns (schema-mapped), cast to the
shared TaskCommitAssociationRow shape used by normalizeTaskCommitAssociation.
*/
const layer = store.asyncLayer!;
const rows = await layer.db
.select()
.from(schema.project.taskCommitAssociations)
.where(eq(schema.project.taskCommitAssociations.taskLineageId, lineageId))
.orderBy(
desc(schema.project.taskCommitAssociations.authoredAt),
desc(schema.project.taskCommitAssociations.createdAt),
);
return (rows as TaskCommitAssociationRow[]).map((row) =>
normalizeTaskCommitAssociation({
...row,
note: row.note ?? undefined,
additions: row.additions ?? undefined,
deletions: row.deletions ?? undefined,
}),
);
}
export async function replaceLegacyTaskCommitAssociationsImpl(store: TaskStore,
lineageId: string,
associations: Array<Omit<TaskCommitAssociation, "id" | "createdAt" | "updatedAt" | "taskLineageId">>,
): Promise<void> {
/*
FNXC:PostgresCutover 2026-07-04:
Backend-mode replacement of legacy-matched task_commit_associations: delete
the legacy-/manual-matched rows for the lineage via async Drizzle, then
re-insert through store.upsertTaskCommitAssociation (which itself dispatches
to the async upsert in backend mode). Canonical-lineage-trailer rows are
preserved (matched only on the three legacy sources), matching the SQLite
path's IN-filter exactly.
*/
const layer = store.asyncLayer!;
await layer.db
.delete(schema.project.taskCommitAssociations)
.where(
and(
eq(schema.project.taskCommitAssociations.taskLineageId, lineageId),
inArray(schema.project.taskCommitAssociations.matchedBy, [
"legacy-task-id-trailer",
"legacy-subject",
"manual-reconciliation",
]),
),
);
for (const association of associations) {
await store.upsertTaskCommitAssociation({ ...association, taskLineageId: lineageId });
}
return;
}