# Lynn V0.79.4 Central Refactor Plan

V0.79.4 is a central refactor release. It must not begin until V0.79.3 has completed verification, packaging, notarization, update-manifest validation, mirror/site validation, and real installer smoke. If V0.79.3 still needs a hotfix, packaging retry, notarization retry, or release-asset correction, this plan stays queued.

## Objectives

- Reduce the central runtime blast radius before adding more features.
- Split `server/routes/chat.js` into a thin route shell plus testable chat modules.
- Decompose `core/engine.js` and `core/session-coordinator.js` around stable injected services.
- Consolidate provider state, fallback, health, and selected-model semantics into one state machine.
- Put local model download, install, load, and health transitions behind a strict IPC boundary.
- Keep security and download policy unchanged or stricter while refactoring.
- Land in small, reversible PRs with targeted tests before the full release gate.

## Entry Criteria

- V0.79.3 is verified, packaged, notarized, and smoked from real release installers.
- `main` or the release integration branch is green on:
  - `npm test`
  - `npm run typecheck`
  - `npm run typecheck:runtime`
  - `npm run build:server`
  - `npm run build:main`
  - `npm run build:renderer`
  - `npm run test:release`
  - `npm run test:release:ui`
- No release-critical V0.79.3 issues remain open.
- No local model artifacts, datasets, training bundles, or release artifacts are pulled into the worktree for this refactor.

## Target Architecture

### 1. Chat Route Split

Current center:

- `server/routes/chat.js` remains the HTTP/WebSocket entry.
- `server/chat/**` already contains typed leaf helpers and should absorb route-internal behavior.

Target modules:

- `server/chat/chat-route-context.ts`
  - Builds the immutable route dependency object from `{ engine, hub, upgradeWebSocket }`.
  - Owns dependency shape only; no stream mutation.
- `server/chat/turn-dispatcher.ts`
  - Handles prompt request validation, session lookup, stream token creation, and handoff to runners.
  - Exposes `dispatchTurn(context, request)`.
- `server/chat/stream-emitters.ts`
  - Owns visible text deltas, trusted deltas, provider metadata events, status events, and buffered flushes.
  - Preserves `shared/ws-events.ts` event names and payload requirements.
- `server/chat/turn-finalizers.ts`
  - Owns close, abort, returned-turn finalization, tool finalization fallback, and hard timeout behavior.
  - Must not synthesize content outside existing retry/fallback policy.
- `server/chat/local-model-bridge.ts`
  - Wraps the direct local model path and maps local runner events into normal chat stream events.
  - Keeps model-specific policy in `local-qwen35-direct-policy.ts` and runner code in the existing runner module.
- `server/chat/title-generator.ts`
  - Moves `generateSessionTitle` out of the route file.

Implementation steps:

1. Add route context and type-only request/stream shapes without changing behavior.
2. Move title generation and pure event emitter helpers first.
3. Move finalization timers and fallback scheduling next.
4. Move local model bridge after stream/finalizer tests are stable.
5. Move turn dispatcher last, leaving `createChatRoute` as route wiring and WebSocket lifecycle only.

Acceptance:

- `server/routes/chat.js` is a route shell, not a behavior hub.
- No public WebSocket event names change.
- Session persistence, title generation, provider metadata, browser status, bridge status, tool cards, and local direct runner behavior remain covered by tests.

Targeted tests:

- `tests/chat-route-events.test.js`
- `tests/chat-utility-state.test.js`
- `tests/session-stream-store.test.js`
- `tests/stream-event-emitter.test.js`
- `tests/stream-state.test.js`
- `tests/provider-route-meta.test.js`
- `tests/local-qwen35-provider-state.test.js`
- `tests/turn-retry-policy.test.js`

### 2. Engine And Session Coordinator Decomposition

Current center:

- `core/engine.js` owns app-level wiring, tool guard wrapping, agent/session managers, provider registry access, and model/runtime services.
- `core/session-coordinator.js` mixes prompt preparation, tool schema policy, skill hints, route/scenario hints, memory relay, session file inspection, reply integrity, and execution orchestration.

Target services:

- `core/services/tool-guard-service.ts`
  - Owns sensitive parameter detection and guarded tool wrapping.
  - Keeps existing deny behavior and audit shape.
- `core/services/model-selection-service.ts`
  - Resolves explicit model refs, utility model refs, provider auth availability, and no-silent-fallback rules.
  - Uses `shared/model-ref.ts` and `shared/known-models.ts`.
- `core/services/prompt-context-service.ts`
  - Builds steer prefix, known-folder aliases, local route hints, scenario hints, skill hints, and file mention hints.
  - Produces plain prompt fragments; no I/O except explicitly injected filesystem readers.
- `core/services/session-memory-service.ts`
  - Handles session relay, memory notification, memory summaries, and session skeleton inspection.
  - Has bounded timeouts and deterministic fallback values.
- `core/services/reply-integrity-service.ts`
  - Owns reply execution tracking and validation.
- `core/session-coordinator.js`
  - Becomes orchestration glue around the services above.
- `core/engine.js`
  - Becomes dependency composition and lifecycle ownership.

Implementation steps:

1. Extract pure services from `session-coordinator` with direct tests before moving I/O behavior.
2. Inject `{ t, getLocale }` into core services instead of importing HTTP-layer i18n from `server/**`.
3. Extract tool guard service from `engine` and keep existing test fixtures.
4. Extract model selection service and run all model isolation/no-fallback tests.
5. Move session memory service last because it crosses filesystem, memory, and agent lifecycle boundaries.

Acceptance:

- `core/**` has no new dependency on `server/**`.
- `SessionCoordinator` exposes the same public constructor/methods during the refactor unless a separate migration note is approved.
- Existing sessions remain readable.
- No selected model silently falls back to another provider/model when the user explicitly selected a model.

Targeted tests:

- `tests/session-coordinator.test.js`
- `tests/session-model-isolation.test.js`
- `tests/session-index.test.js`
- `tests/model-no-fallback.test.js`
- `tests/model-composite-key.test.js`
- `tests/model-sync.test.js`
- `tests/model-sync-routes.test.js`
- `tests/model-tool-capabilities.test.js`
- `tests/security-guard.test.js`

### 3. Provider State Machine Consolidation

Current centers:

- `core/provider-registry.ts`
- `server/routes/providers.js`
- `server/routes/models.ts`
- `core/model-sync.ts`
- desktop provider settings state
- chat provider metadata events

Target:

- Add `shared/provider-state.ts` as the canonical state contract.
- Add `core/provider-state-machine.ts` as the only transition authority.
- Treat state as a derived snapshot from registry config, auth availability, health result, selected model, cooldown/fallback state, and local model runtime status.

Canonical states:

- `unconfigured`
- `needs_auth`
- `ready`
- `checking`
- `degraded`
- `cooldown`
- `fallback_active`
- `disabled`
- `error`

Required transition inputs:

- provider registry entry changed
- auth credential added, changed, or removed
- model list refreshed
- health check started or completed
- route fallback recorded
- cooldown window started or expired
- selected model changed
- local runtime state changed

Required output shape:

- provider id
- display name
- state
- selected model ref
- auth status
- health status
- fallback chain metadata
- cooldown reason class
- last checked timestamp
- user-visible safe reason

Implementation steps:

1. Add shared state type and pure reducer tests.
2. Adapt server provider/model routes to read reducer output while preserving response shape.
3. Adapt chat provider metadata creation to use canonical fallback/cooldown fields.
4. Adapt desktop provider settings store to consume canonical state.
5. Remove duplicate state derivation once parity tests pass.

Acceptance:

- Provider list, model health, chat fallback chip, and selected-model routes agree on the same state.
- OAuth providers, no-auth local providers, API-key providers, and coding providers all pass registry tests.
- No provider secret appears in state payloads, logs, snapshots, or test fixtures.

Targeted tests:

- `tests/provider-registry-crud.test.js`
- `tests/provider-registry-oauth.test.js`
- `tests/provider-route-meta.test.js`
- `tests/model-sync.test.js`
- `tests/model-sync-routes.test.js`
- `tests/model-no-fallback.test.js`
- `tests/local-qwen35-provider-state.test.js`

### 4. Local Model Manager IPC Boundary

Current centers:

- `core/model-manager.js`
- `desktop/model-downloader.cjs`
- desktop provider settings UI
- local model chat runner path

Target:

- Main process owns local model download/install/load/stop/health operations.
- Server/runtime process receives only sanitized local model state snapshots and sends explicit IPC requests.
- Renderer receives only display-safe status, progress, and action availability.
- No renderer or chat route path gets direct filesystem or network authority for model artifacts.

Boundary modules:

- `shared/local-model-ipc.ts`
  - Request and response types.
  - Safe progress shape.
  - Error reason enum with user-safe messages.
- `desktop/local-model-manager-main.cjs`
  - Main-process implementation wrapper around downloader and local runtime controls.
- `core/local-model-manager-client.ts`
  - Server/runtime client that requests state transitions through IPC.
- `desktop/src/react/stores/local-model-slice.ts`
  - Renderer state consumer only.

Required operations:

- `getState`
- `refreshCatalog`
- `prepareModel`
- `cancelPrepare`
- `startModel`
- `stopModel`
- `healthCheck`

Required states:

- `idle`
- `catalog_ready`
- `preparing`
- `prepared`
- `starting`
- `running`
- `stopping`
- `failed`
- `blocked_by_policy`

Implementation steps:

1. Define shared IPC contract and tests for payload sanitization.
2. Wrap existing downloader with the contract without changing UI behavior.
3. Move renderer actions to IPC calls only.
4. Move server local model manager calls to IPC client only.
5. Add policy checks before any network or filesystem operation.

Acceptance:

- Model status tests never require real model downloads.
- Progress payloads do not expose local paths, URLs, credentials, or raw errors.
- Cancelling and retrying model preparation has deterministic state transitions.
- The local direct runner can only start after a `running` or explicitly healthy state.

Targeted tests:

- `tests/model-downloader-safety.test.js`
- `tests/llamacpp-profiles.test.js`
- `tests/local-qwen35-provider-state.test.js`
- new IPC contract tests for request validation and sanitized state output

### 5. Security And Download Policy

Security policy is part of the refactor, not a later audit.

Rules:

- Do not weaken DNS rebinding, private-network blocking, symlink TOCTOU checks, or sensitive path denial.
- Do not add new direct binary/model download paths in renderer, chat route, or provider routes.
- Do not log secrets, local paths that identify a person, raw auth headers, cookies, API keys, OAuth tokens, provider keys, or signed URLs.
- Do not put release artifact links, local artifact paths, model acquisition instructions, or machine-specific endpoints in docs or fixtures.
- Keep provider credentials in the existing secure storage path; reducer and state snapshots get only boolean/auth class summaries.
- Keep child-process environment allowlists strict and move provider secrets toward private closure-owned modules where practical.
- Every new IPC request must validate operation, provider/model id, state preconditions, and policy gate result.

Security implementation steps:

1. Add sanitizers for provider state, local model state, and IPC error payloads.
2. Add deny-by-default request validation for local model IPC operations.
3. Add regression tests proving blocked downloads do not start network or filesystem writes.
4. Re-run filesystem and security route tests after chat and core extraction.
5. Add a docs grep gate before release to catch forbidden artifact/link patterns.

Targeted tests:

- `tests/security-guard.test.js`
- `tests/security-session-sync.test.js`
- `tests/tool-authorization-session-path.test.js`
- `tests/model-downloader-safety.test.js`
- `tests/model-no-fallback.test.js`

## Rollout Order

1. Baseline branch
   - Confirm V0.79.3 is fully released and smoked.
   - Open V0.79.4 central-refactor branch.
   - Run baseline targeted tests and capture counts in the PR description.
2. Chat shell PR
   - Extract route context, title generation, stream emitters, and finalizers.
   - Keep `createChatRoute` signature stable.
3. Chat dispatcher PR
   - Extract turn dispatcher and local model bridge.
   - Run chat, stream, provider metadata, and local model targeted tests.
4. Provider state PR
   - Add shared state contract and reducer.
   - Adapt provider/model routes and chat provider metadata.
5. Desktop provider state PR
   - Move provider settings UI/store to canonical state.
   - Keep visual wording changes minimal.
6. Local model IPC PR
   - Add shared IPC contract.
   - Move model preparation/start/stop behind main-process boundary.
7. Core service PR
   - Extract tool guard, prompt context, reply integrity, model selection, and session memory services.
   - Remove new or existing `core/**` reverse dependencies on `server/**` where touched.
8. Integration PR
   - Remove dead adapters and duplicate state derivation.
   - Run full gates.
   - Update release notes only after behavior parity is verified.

## Regression Gates

Per PR minimum:

- `npm run typecheck:runtime`
- Targeted Vitest files listed in the relevant section.
- `npm run build:server` when server/runtime code changes.
- `npm run build:renderer` when desktop React state or settings UI changes.

Integration gate:

- `npm test`
- `npm run typecheck`
- `npm run typecheck:runtime`
- `npm run build:server`
- `npm run build:main`
- `npm run build:renderer`
- `npm run test:release`
- `npm run test:release:ui`

Manual gate:

- Real chat turn with selected cloud provider.
- Real chat turn with selected local provider using existing prepared local state only.
- Provider fallback metadata visible and accurate.
- Provider settings can add, edit, test, disable, and re-enable a provider.
- Local model preparation cancel/retry states render without exposing sensitive details.
- Security settings and filesystem authorization still block disallowed paths.
- No UI overlap in home, chat, provider settings, security settings, and local model panels.

Docs/link sanity:

- Grep changed docs for IP address literals, local home paths, model artifact extensions, release artifact extensions, raw tokens, and signed URL markers.
- Grep changed docs for direct model acquisition instruction patterns.
- Grep changed code/tests for provider secrets in fixtures and snapshots.

## Explicit Non-Goals

- No feature additions beyond preserving existing behavior through clearer module boundaries.
- No model quality tuning, prompt rewrite, routing policy change, or cascade order change.
- No replacement of the provider registry storage format unless a compatibility migration is separately approved.
- No new local model, benchmark model, dataset, BF16/GGUF artifact, or training package.
- No renderer-side network authority for model artifacts.
- No release packaging redesign.
- No update-manifest or mirror/site workflow redesign.
- No public WebSocket event rename.
- No session file format migration unless required by a tested compatibility shim.
- No broad TypeScript conversion outside files touched by the planned boundary extractions.

## Done Definition

- `server/routes/chat.js`, `core/engine.js`, and `core/session-coordinator.js` are reduced to composition/orchestration roles.
- Provider state is derived through one reducer and shared across server, chat metadata, and desktop settings.
- Local model operations cross one validated IPC boundary.
- Security and download policies are at least as strict as V0.79.3.
- Full integration gates pass.
- The release owner can start normal V0.79.4 package/notarization/release validation without special local artifacts or machine-specific setup.
