Files
fusion/packages/engine/src/effective-settings.ts
gsxdsm ba1e82381e fix(FN-7952): cut runtime services over to PostgreSQL (#2109)
## Summary

Engine and dashboard traffic now stays on the authoritative PostgreSQL
layer across execution, recovery, project discovery, planning sessions,
analytics, and shutdown. The dashboard no longer presents a migration
notice for a cutover that is already mandatory.

## Design decisions

- Runtime composition requires an async data layer instead of
constructing a hidden SQLite fallback.
- Engine workflow, mission, claim, and self-healing reads await their
PostgreSQL-backed store contracts.
- Project-scoped dashboard stores retain and close their backend owner
exactly once.
- The dashboard test quarantine entry remains paired with its Vitest
exclusion, preserving the repository’s deletion-ratchet policy.

## Validation

- Core, Engine, Dashboard, CLI, and Desktop typechecks pass on the
stacked branch.
- `pnpm test:gate` passes all 478 gate tests.
- This PR changes 62 files.

## Stack

- Depends on #2108.
- CLI/desktop/ops, plugins, and docs/release follow in later PRs.

Related: #2105


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

## Summary by CodeRabbit

* **New Features**
* Project discovery now recognizes projects using the
`.fusion/project.json` marker.
* Knowledge indexing and search are more reliable across project-scoped
storage.
* **Bug Fixes**
* Improved session, audit timeline, approval, monitoring, and analytics
data consistency.
* Prevented stale planning-session updates and project-store shutdown
races.
* Ensured chat usage and CLI session status are saved before continuing.
* **UI Changes**
* Removed the storage migration notice banner now that the PostgreSQL
transition is complete.
* **Reliability**
* Improved shutdown handling, workflow execution, and worktree behavior.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-14 22:16:16 -07:00

62 lines
2.2 KiB
TypeScript

/**
* Engine-side helper: merge per-task EFFECTIVE workflow settings (U3, KTD-3) over a
* base settings object fetched from the store, so the engine's flat
* `settings.<key>` read sites pick up workflow-setting values with zero changes at
* the read sites.
*
* TWO-TIER MERGE (the parity-preserving rule):
* - a STORED workflow value ALWAYS overrides the base (the workflow tuned it);
* - a declaration-DEFAULT-only key (no stored value) only FILLS the base when the
* base lacks the key.
*
* This is what keeps U3 behavior-identical BEFORE the U4 hard-move: a customized
* project setting still present in the base is NOT clobbered by a declaration
* default; only a real stored workflow value overrides it. After the hard-move the
* base lacks the moved key, so the declaration default fills it. Absent-default
* model lanes contribute nothing, so they never override a real project value.
*
* `resolveEffectiveSettingsDetailed` never throws (degrades to declaration
* defaults), so this helper is a thin store-coupled wrapper that also never throws.
*/
import {
applyWorkflowSettingsOverlay,
resolveEffectiveSettingsDetailed,
type Settings,
type TaskStore,
} from "@fusion/core";
/** The minimal task shape the resolver needs. Task carries no projectId field —
* the project key is derived from the store. */
export interface EffectiveSettingsTask {
id: string;
}
/**
* Merge `base` with the task's effective workflow settings via the two-tier rule
* (stored overrides; default-only fills only-absent). Returns a NEW object; `base`
* is not mutated. Degrades to returning `base` unchanged on any resolver error.
*/
export async function mergeEffectiveSettings<T extends Partial<Settings>>(
store: Pick<
TaskStore,
| "getTaskWorkflowSelection"
| "getTaskWorkflowSelectionAsync"
| "getWorkflowDefinition"
| "getWorkflowSettingValues"
| "getWorkflowSettingsProjectId"
>,
task: EffectiveSettingsTask,
base: T,
): Promise<T> {
try {
const detailed = await resolveEffectiveSettingsDetailed(
store as Parameters<typeof resolveEffectiveSettingsDetailed>[0],
task,
);
return applyWorkflowSettingsOverlay(base, detailed);
} catch {
return base;
}
}