Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
262f57d
Introduce state sync engine seam and LiveStore shadow mirror
cursoragent Feb 20, 2026
8d0af1f
Add snapshot parity utility for LiveStore migration
cursoragent Feb 20, 2026
ccacb4b
Add wsServer coverage for injected state sync engine
cursoragent Feb 20, 2026
fa39092
Cover message events in shadow sync engine parity test
cursoragent Feb 20, 2026
dae7ab8
Project mirrored state events into LiveStore parity snapshot
cursoragent Feb 20, 2026
40bb6cb
Add LiveStore read-pilot sync engine with delegate fallback
cursoragent Feb 20, 2026
637eae0
Add web state source seam for sync provider migration
cursoragent Feb 20, 2026
38ca95a
Add bootstrap parity diagnostics for LiveStore read pilot
cursoragent Feb 20, 2026
2e2af4c
Document sync engine migration runtime flags
cursoragent Feb 20, 2026
89e2c73
Add shadow engine parity integration coverage
cursoragent Feb 20, 2026
fc14fdb
Expand shadow parity coverage in persistence and ws tests
cursoragent Feb 20, 2026
cbfe1e3
Add source-labelled read-pilot logging and recovery test
cursoragent Feb 20, 2026
7a9b733
Add optional catch-up parity diagnostics for read pilot
cursoragent Feb 20, 2026
5001314
Add websocket integration coverage for read-pilot ordering
cursoragent Feb 20, 2026
6513750
Add websocket coverage for read-pilot fallback to legacy
cursoragent Feb 20, 2026
1bffbcf
Add web state-source mode seam for read-pilot
cursoragent Feb 20, 2026
c2e35cb
Add sync-engine migration rollout runbook
cursoragent Feb 20, 2026
b388127
Default sync engine mode to read-pilot with fallback
cursoragent Feb 20, 2026
09f93f3
Extract and test sync engine mode resolution
cursoragent Feb 20, 2026
8a6676c
Add optional listMessages parity diagnostics in read pilot
cursoragent Feb 20, 2026
6aa5a59
Expose sync engine mode in server config and web source selection
cursoragent Feb 20, 2026
08d4dcb
Add optional shadow bootstrap parity diagnostics
cursoragent Feb 20, 2026
c0c5c8f
Add strict read-pilot mode without delegate fallback
cursoragent Feb 20, 2026
75cdd78
Add websocket coverage for strict read-pilot error mode
cursoragent Feb 20, 2026
24dbfa5
Add optional shadow catchup and listMessages parity checks
cursoragent Feb 20, 2026
04f3546
Add contracts coverage for sync engine mode in server config
cursoragent Feb 20, 2026
3b20cc7
Add livestore strict mode and wire mode mapping across stack
cursoragent Feb 20, 2026
bba3afe
Add read-pilot route metrics and fallback counters
cursoragent Feb 20, 2026
8b8ce91
Infer sync engine mode from injected state engine
cursoragent Feb 20, 2026
e7d37a3
Add enforce-liveStore startup guard for sync engine mode
cursoragent Feb 20, 2026
842435e
Promote strict livestore mode to default sync engine
cursoragent Feb 20, 2026
93686a8
Keep read-pilot as default pending durable mirror bootstrap
cursoragent Feb 20, 2026
2dd8735
Bootstrap LiveStore mirror from persisted catch-up history
cursoragent Feb 20, 2026
07f4dbf
Await mirror replay during startup hydration
cursoragent Feb 20, 2026
9a65756
Propagate mirror replay success status during bootstrap
cursoragent Feb 20, 2026
7ee0cce
Restore strict livestore as default after startup hydration hardening
cursoragent Feb 20, 2026
0611b82
Default server config contract to strict livestore mode
cursoragent Feb 20, 2026
c777aae
Cover mirror bootstrap replay against real legacy state
cursoragent Feb 20, 2026
672551f
Centralize catch-up and list-message parity diff helpers
cursoragent Feb 20, 2026
9a56a22
Infer strict livestore mode from read-pilot fallback policy
cursoragent Feb 20, 2026
f088858
Remove client-side legacy state-source mode seam
cursoragent Feb 20, 2026
c3975c1
Warn when mirror reports unsuccessful state-event writes
cursoragent Feb 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,45 @@ T3 Code has a global runtime mode switch in the chat toolbar:
- **Full access** (default): starts sessions with `approvalPolicy: never` and `sandboxMode: danger-full-access`.
- **Supervised**: starts sessions with `approvalPolicy: on-request` and `sandboxMode: workspace-write`, then prompts in-app for command/file approvals.

## Sync engine migration modes

The server supports feature-flagged sync-engine modes while migrating from the legacy in-house state sync pipeline to LiveStore.

- `T3CODE_SYNC_ENGINE_MODE=livestore` (default)
- Uses the LiveStore-backed read path and disables delegate fallback (strict mirror-read mode).
- Mirror state is pre-bootstrapped from persisted catch-up history during server startup.
- `T3CODE_SYNC_ENGINE_MODE=livestore-read-pilot`
- Uses the LiveStore mirror for `state.bootstrap`, `state.catchUp`, and `state.listMessages` reads when available.
- Automatically falls back to legacy reads if the mirror is unavailable or errors.
- `T3CODE_SYNC_ENGINE_MODE=legacy`
- Uses the existing `PersistenceService`-backed state sync engine for reads and writes.
- `T3CODE_SYNC_ENGINE_MODE=shadow`
- Keeps legacy state as canonical, but mirrors committed `state.event` traffic into a LiveStore shadow store for parity validation.

- `T3CODE_LIVESTORE_ENFORCE_MODE=1`
- Disallows `legacy` and `shadow` sync modes at startup.
- Intended for post-cutover environments to prevent accidental rollback to pre-LiveStore modes.

Optional diagnostics:

- `T3CODE_LIVESTORE_BOOTSTRAP_PARITY_CHECK=1`
- In `livestore-read-pilot` mode, compares LiveStore vs legacy `state.bootstrap` snapshots and logs drift diagnostics.
- `T3CODE_LIVESTORE_CATCHUP_PARITY_CHECK=1`
- In `livestore-read-pilot` mode, compares LiveStore vs legacy `state.catchUp` responses and logs drift diagnostics.
- `T3CODE_LIVESTORE_LIST_MESSAGES_PARITY_CHECK=1`
- In `livestore-read-pilot` mode, compares LiveStore vs legacy `state.listMessages` responses and logs drift diagnostics.
- `T3CODE_LIVESTORE_SHADOW_BOOTSTRAP_PARITY_CHECK=1`
- In `shadow` mode, compares mirror vs delegate `state.bootstrap` snapshots and logs drift diagnostics.
- `T3CODE_LIVESTORE_SHADOW_CATCHUP_PARITY_CHECK=1`
- In `shadow` mode, compares mirror vs delegate `state.catchUp` responses and logs drift diagnostics.
- `T3CODE_LIVESTORE_SHADOW_LIST_MESSAGES_PARITY_CHECK=1`
- In `shadow` mode, compares mirror vs delegate `state.listMessages` responses and logs drift diagnostics.
- `T3CODE_LIVESTORE_DISABLE_READ_FALLBACK=1`
- In `livestore-read-pilot` mode, disables delegate read fallback and fails requests when mirror reads fail (strict canary mode).
- Read-source/fallback counters are emitted on shutdown via `livestore read pilot metrics` logs.

Web clients now always consume the server-authoritative `api.state.*` stream, and the server mode fully controls LiveStore vs fallback behavior.

## Provider architecture

The web app communicates with the server via WebSocket using a simple JSON-RPC-style protocol:
Expand Down
80 changes: 80 additions & 0 deletions SYNC_ENGINE_MIGRATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Sync Engine Migration Runbook

This runbook describes how to operate and verify the LiveStore migration path safely.

## Runtime modes

Server mode is controlled by `T3CODE_SYNC_ENGINE_MODE`:

- `livestore` (default): strict LiveStore read path (delegate read fallback disabled).
- `livestore-read-pilot`: write path remains legacy-backed, but state reads prefer LiveStore mirror with delegate fallback.
- `legacy`: legacy persistence service is canonical for reads/writes.
- `shadow`: legacy remains canonical, and `state.event` writes are mirrored into LiveStore.

Startup behavior:

- when running `shadow`, `livestore-read-pilot`, or `livestore`, the mirror is bootstrapped from persisted catch-up history before serving requests.

Optional safety gate:

- `T3CODE_LIVESTORE_ENFORCE_MODE=1`
- prevents startup in `legacy` or `shadow` mode.
- use in post-cutover environments to block accidental rollback.

## Diagnostics flags

- `T3CODE_LIVESTORE_BOOTSTRAP_PARITY_CHECK=1`
- In read-pilot mode, compares `state.bootstrap` mirror output against delegate output.
- `T3CODE_LIVESTORE_CATCHUP_PARITY_CHECK=1`
- In read-pilot mode, compares `state.catchUp` mirror output against delegate output.
- `T3CODE_LIVESTORE_LIST_MESSAGES_PARITY_CHECK=1`
- In read-pilot mode, compares `state.listMessages` mirror output against delegate output.
- `T3CODE_LIVESTORE_SHADOW_BOOTSTRAP_PARITY_CHECK=1`
- In shadow mode, compares `state.bootstrap` mirror output against delegate output.
- `T3CODE_LIVESTORE_SHADOW_CATCHUP_PARITY_CHECK=1`
- In shadow mode, compares `state.catchUp` mirror output against delegate output.
- `T3CODE_LIVESTORE_SHADOW_LIST_MESSAGES_PARITY_CHECK=1`
- In shadow mode, compares `state.listMessages` mirror output against delegate output.
- `T3CODE_LIVESTORE_DISABLE_READ_FALLBACK=1`
- In read-pilot mode, disables delegate read fallback for strict mirror-read canary validation.
- Web clients consume the server-authoritative `api.state.*` stream directly; server mode now controls fallback behavior.

## Suggested rollout sequence

1. **Baseline**
- Run in `legacy`.
- Ensure no outstanding state-sync regressions in CI.
2. **Shadow validation window**
- Enable `T3CODE_SYNC_ENGINE_MODE=shadow`.
- Watch logs for mirror commit failures.
- Validate parity fixtures and targeted tests.
3. **Read pilot**
- Enable `T3CODE_SYNC_ENGINE_MODE=livestore-read-pilot`.
- Start with parity flags enabled in staging.
- Verify bootstrap/catch-up/list-message fallback behavior under induced mirror failures.
4. **Confidence window**
- Keep parity flags enabled until drift warnings are consistently absent.
- Track fallback frequency (should trend toward zero in healthy conditions).
- Review `livestore read pilot metrics` shutdown logs for route-level source and fallback counts.

## Phase-5 cleanup readiness checklist

Before removing legacy-only sync plumbing:

- [ ] Read-pilot mode has stable production/staging behavior for a full confidence window.
- [ ] Bootstrap and catch-up parity checks show no unexplained drift.
- [ ] Fallback-to-delegate behavior has been exercised and observed as safe.
- [ ] Web client state-source mode seam is present and default behavior remains backwards-compatible.
- [ ] Parity and integration tests cover:
- [ ] project/thread/message lifecycle parity
- [ ] checkpoint revert parity
- [ ] websocket bootstrap/catch-up ordering in read-pilot
- [ ] websocket read fallback under mirror failures

## Final cutover notes

When promoting LiveStore path to default:

- keep legacy fallback for at least one release window;
- remove fallback only after confirming operational metrics and parity diagnostics remain healthy;
- remove legacy-only state sync glue in a dedicated cleanup PR to keep risk isolated and reviewable.
2 changes: 2 additions & 0 deletions apps/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
"test": "vitest run"
},
"dependencies": {
"@livestore/adapter-node": "^0.3.1",
"@livestore/livestore": "^0.3.1",
"@pierre/diffs": "^1.1.0-beta.16",
"node-pty": "^1.1.0",
"open": "^10.1.0",
Expand Down
76 changes: 76 additions & 0 deletions apps/server/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@ import { fileURLToPath } from "node:url";

import { fixPath } from "./fixPath";
import { createLogger } from "./logger";
import { bootstrapMirrorFromCatchUp } from "./livestore/mirrorBootstrap";
import { LiveStoreStateMirror } from "./livestore/liveStoreEngine";
import { PersistenceService } from "./persistenceService";
import { LegacyStateSyncEngine } from "./stateSyncEngineLegacy";
import { LiveStoreReadPilotStateSyncEngine } from "./stateSyncEngineLiveStoreReadPilot";
import { ShadowStateSyncEngine } from "./stateSyncEngineShadow";
import { assertSyncEngineModeAllowed, resolveSyncEngineMode } from "./syncEngineMode";
import { createServer } from "./wsServer";

fixPath();
Expand Down Expand Up @@ -110,10 +116,69 @@ async function main() {
requestedPort ?? (mode === "desktop" ? DEFAULT_PORT : await findAvailablePort(DEFAULT_PORT));
const legacyStateDir = resolveStateDir(process.env.T3CODE_STATE_DIR);
const stateDbPath = resolveStateDbPath(process.env.T3CODE_STATE_DB_PATH);
const syncEngineMode = resolveSyncEngineMode(process.env.T3CODE_SYNC_ENGINE_MODE);
const enforceLiveStoreOnly =
parseBooleanEnv(process.env.T3CODE_LIVESTORE_ENFORCE_MODE) ?? false;
assertSyncEngineModeAllowed(syncEngineMode, {
enforceLiveStoreOnly,
});
const enableLiveStoreBootstrapParityCheck =
parseBooleanEnv(process.env.T3CODE_LIVESTORE_BOOTSTRAP_PARITY_CHECK) ?? false;
const enableLiveStoreCatchUpParityCheck =
parseBooleanEnv(process.env.T3CODE_LIVESTORE_CATCHUP_PARITY_CHECK) ?? false;
const enableLiveStoreListMessagesParityCheck =
parseBooleanEnv(process.env.T3CODE_LIVESTORE_LIST_MESSAGES_PARITY_CHECK) ?? false;
const enableLiveStoreShadowBootstrapParityCheck =
parseBooleanEnv(process.env.T3CODE_LIVESTORE_SHADOW_BOOTSTRAP_PARITY_CHECK) ?? false;
const enableLiveStoreShadowCatchUpParityCheck =
parseBooleanEnv(process.env.T3CODE_LIVESTORE_SHADOW_CATCHUP_PARITY_CHECK) ?? false;
const enableLiveStoreShadowListMessagesParityCheck =
parseBooleanEnv(process.env.T3CODE_LIVESTORE_SHADOW_LIST_MESSAGES_PARITY_CHECK) ?? false;
const disableLiveStoreDelegateReadFallback =
parseBooleanEnv(process.env.T3CODE_LIVESTORE_DISABLE_READ_FALLBACK) ?? false;
const persistenceService = new PersistenceService({
dbPath: stateDbPath,
legacyProjectsJsonPath: path.join(legacyStateDir, "projects.json"),
});
const legacyStateSyncEngine = new LegacyStateSyncEngine({ persistenceService });
const liveStoreMirror = new LiveStoreStateMirror({
storeId: `t3-shadow-${mode}`,
});
const isReadPilotMode = syncEngineMode === "livestore-read-pilot" || syncEngineMode === "livestore";
if (syncEngineMode === "shadow" || isReadPilotMode) {
const bootstrapResult = await bootstrapMirrorFromCatchUp({
source: legacyStateSyncEngine,
mirror: liveStoreMirror,
logger,
failOnError: syncEngineMode === "livestore",
});
logger.info("bootstrapped livestore mirror from persisted catch-up history", {
mirroredCount: bootstrapResult.mirroredCount,
lastStateSeq: bootstrapResult.lastStateSeq,
complete: bootstrapResult.complete,
syncEngineMode,
});
}
const stateSyncEngine =
syncEngineMode === "shadow"
? new ShadowStateSyncEngine({
delegate: legacyStateSyncEngine,
mirror: liveStoreMirror,
enableBootstrapParityCheck: enableLiveStoreShadowBootstrapParityCheck,
enableCatchUpParityCheck: enableLiveStoreShadowCatchUpParityCheck,
enableListMessagesParityCheck: enableLiveStoreShadowListMessagesParityCheck,
})
: isReadPilotMode
? new LiveStoreReadPilotStateSyncEngine({
delegate: legacyStateSyncEngine,
mirror: liveStoreMirror,
enableBootstrapParityCheck: enableLiveStoreBootstrapParityCheck,
enableCatchUpParityCheck: enableLiveStoreCatchUpParityCheck,
enableListMessagesParityCheck: enableLiveStoreListMessagesParityCheck,
disableDelegateReadFallback:
syncEngineMode === "livestore" ? true : disableLiveStoreDelegateReadFallback,
})
: legacyStateSyncEngine;
const devUrl = process.env.VITE_DEV_SERVER_URL;
const noBrowser = parseBooleanEnv(process.env.T3CODE_NO_BROWSER) ?? mode === "desktop";
const authToken = process.env.T3CODE_AUTH_TOKEN;
Expand All @@ -132,6 +197,8 @@ async function main() {
staticDir,
devUrl,
persistenceService,
stateSyncEngine,
syncEngineMode,
authToken,
});
await server.start();
Expand All @@ -143,6 +210,15 @@ async function main() {
mode,
stateDbPath,
legacyStateDir,
syncEngineMode,
enforceLiveStoreOnly,
liveStoreBootstrapParityCheck: enableLiveStoreBootstrapParityCheck,
liveStoreCatchUpParityCheck: enableLiveStoreCatchUpParityCheck,
liveStoreListMessagesParityCheck: enableLiveStoreListMessagesParityCheck,
liveStoreShadowBootstrapParityCheck: enableLiveStoreShadowBootstrapParityCheck,
liveStoreShadowCatchUpParityCheck: enableLiveStoreShadowCatchUpParityCheck,
liveStoreShadowListMessagesParityCheck: enableLiveStoreShadowListMessagesParityCheck,
liveStoreDisableReadFallback: disableLiveStoreDelegateReadFallback,
authEnabled: Boolean(authToken),
});

Expand Down
Loading
Loading