Status: audit done + plan approved 2026-07-21; execution complete 2026-07-22 — the extraction and everything sequenced on it shipped (engine, providers seam, contribution cards, Spaces UX, meeting, memory, voice, companion; the final hardening/second-provider pass remains). Kept as the execution contract of record; the “Known doc drift” list at the bottom was addressed in the per-subsystem doc updates. Vision 00 · spec 01 · roadmap 10 (see its “Where we are” table) · UX 11.
No rewrite. Extract the proven interview vertical slice into a generic
conversation engine; add modes as configuration. The riskiest asset is the
concurrency discipline in sessionManager.ts — the answering/answerAbort
slot-ownership rules (sessionManager.ts:512-521),
the synchronous slot claim before the classify await, abort-before-broadcast
ordering, delete-then-insert answer replacement only after stream completion,
and the follow-up generation guard. These are pinned by parity tests
(sessionManager.parity.test.ts) before anything moves.
Space in the UI, ContextPack internally (jobs → packs of kind='job';
kinds: job|subject|project|meeting|personal|game|custom).
SessionMode = interview|practice|interviewer_assist|meeting|tutor|companion.
Every output becomes a Contribution (planned → streaming → completed →
dismissed/accepted/corrected/failed), kinds: answer, code, context,
action_item, open_question, suggested_question, coverage, warning,
tutor_prompt, memory_suggestion, summary. Speaker → you|them|agent|unknown
(old rows mapped at read). The LLM classifies salience; deterministic gates
(confidence, cooldown, presence, mute, budget) decide whether to speak.
Providers are capability interfaces (Chat / Embedding / RealtimeStt /
BatchStt / Speech / Vision), never one god interface. These supersede the older
“Context Pack (UI)” wording in 01-PRD/11-UX and the roadmap’s Phase-2
multi-provider slot (seam is in place; a second provider is still deferred).
src/main/services/engine/ src/main/providers/
engine.ts engineSession.ts modeDefinition.ts types.ts registry.ts errors.ts
contextEvent.ts contribution.ts grounding.ts openai/{chat,embeddings,realtimeStt,
sourceAdapter.ts persistence/ batchStt,speech,vision}.ts
trigger/{triggerPolicy,reactiveQuestionPolicy,summonedPolicy}.ts
modes/interview.mode.ts // persona, scenario config, follow-up, STAR cues
sessionManager.ts stays as a thin facade; every existing IPC channel keeps
its exact signature. AnswerEvent (delta/meta/usage) is already shared across
answer/coding/vision and becomes the provider-neutral ChatEvent.
| Mig | Contents | Risk |
|---|---|---|
| 0008 ✅ | Rename went LOGICAL, not physical (shipped 2026-07-21): TS/table objects are contextPacks/packId but physical names stay jobs/job_id — drizzle maps them, so no table rebuild, no FK re-pointing, no fkRebuild.ts changes, and db:generate stayed non-interactive. Shipped as three ALTERs (+jobs.kind default ‘job’, +sessions.mode default ‘interview’, −profiles.answer_style) + a mode backfill UPDATE (live→interview, mock/sparring→practice) |
Was High under the physical plan; Low as shipped — plus fixture-DB test + backup-before-migrate |
| 0009 | contributions table; interview answers dual-write (ai_answers stays source of truth) |
Low |
| 0010 | embeddings.provider (default 'openai'; model+dim already stored); mismatch → explicit re-index requirement |
Low |
Speaker strings stay on disk; repos map old↔new at read. No row deletion
anywhere; the only destructive DDL is the provably-dead answer_style.
engine:* +
contribution events are additive; an adapter dual-emits legacy
answerDelta/Meta/Done/Reset/Followup until the overlay migrates..ipc.ts) are extracted to one shared module
alongside it.answerCards.ts seeds the ContributionCard store (isCoding →
kind); unknown kinds render a safe fallback card. Frozen e2e selectors:
window sizes, /Stop|Pause/ button text, the Interviews h3.Typecheck + build + full unit suite; phase boundaries also run
scripts/privacy-affinity/hardtest.js. Baseline guardrails:
src/main/test/architecture.test.ts (renderer/preload/shared can never import
OpenAI/DB/electron-main modules; built renderer bundle carries no
api.openai.com/key-store/DB markers) and
sessionManager.parity.test.ts (start → transcript persistence → classify
gate → retrieval → stream order → persistence → stop, plus the stale-abort
race). Test DB: real drizzle migrations on in-memory sql.js
(src/main/test/dbHarness.ts — better-sqlite3 is Electron-ABI and can’t load
under vitest); the harness’s fixture param is the migration-test
entry point.
feat/v2-baseline — lockfile metadata repair + the guardrail
tests above. Zero runtime changes.feat/v2-schema-packs — 0008 + backup-before-migrate + shared
zod enums + repo aliases + fixture test; docs 04/05 regenerated.feat/v2-engine — engine extraction + 0009; golden
event-sequence parity; docs 02/05/06.feat/v2-providers-seam — capability interfaces, OpenAI as
reference impl, 0010; engine-on-fakes suite.feat/v2-contribution-cards — overlay decomposition.04-DATABASE: lists dropped ai_answers columns, omits sessions.kind and the
answer_feedback table, claims a nonexistent transcript_chunk_id FK, stale
enums/settings keys. 05-IPC-MAP: missing story_teller, phantom rich
answer-meta payload, missing answer-followup/transcriber-status/
overlay-clickthrough, stale domain list. 03-WINDOWS: overlay event table
lists 6 of ~18 subscriptions. 06-OPENAI-SERVICE: wrong classifier type name
(ClassifiedQuestion + isQuestion), contradicted coding output order,
missing tailor preset/effort and its 300s/no-retry override.
02-ARCHITECTURE: vectors/ dir is provisioned but unused (embeddings live
in-DB).