From 7ad2d6ef94017eaa62ff4b18e5cb739d268f403e Mon Sep 17 00:00:00 2001 From: Aaron Queen Date: Sat, 10 Oct 2026 18:21:06 -0600 Subject: [PATCH 1/2] refactor: name the runtime-control option for old servers plainly stopRuntime takes oldServersStopped, meaning the caller has confirmed every old-version MCP server and fallback writer is stopped. The earlier name, legacyQuiescent, is still accepted until the installer that calls it moves to the new one. Docs and README say what the option means instead of using the word quiescence. --- README.md | 4 ++-- __tests__/runtime-control.test.ts | 5 +++-- site/src/content/docs/reference/api.md | 9 +++++---- src/runtime-control.ts | 13 +++++++++---- 4 files changed, 19 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index ba5d29e81..3635bd3d2 100644 --- a/README.md +++ b/README.md @@ -865,8 +865,8 @@ the graph API. Its functions are also exported from the package entry point. reports stop outcomes, and reserves the writer slot for a live updater. A replacement bootstrap claims that reservation before running its CLI in the same process. Keep the coordination module available separately from the executable -being replaced, including during rollback. Live legacy writers require explicit -session quiescence before cutover. See the [runtime control contract](site/src/content/docs/reference/api.md#installer-runtime-control). +being replaced, including during rollback. Live old-version writers require explicit +confirmation that every such server is stopped before cutover. See the [runtime control contract](site/src/content/docs/reference/api.md#installer-runtime-control). For ordinary checkout startup, `startRuntimeWatcher(root, { expectedVersion, cliPath, runtimePath?, timeoutMs? })` reuses or elects a shared daemon without diff --git a/__tests__/runtime-control.test.ts b/__tests__/runtime-control.test.ts index 5d2f4f625..1b18fa45f 100644 --- a/__tests__/runtime-control.test.ts +++ b/__tests__/runtime-control.test.ts @@ -264,7 +264,8 @@ describe('runtime identity and readiness', () => { }, ); - it('preserves a legacy daemon by default, then stops it only after explicit quiescence', async () => { + it.each(['oldServersStopped', 'legacyQuiescent'] as const)( + 'preserves a legacy daemon by default, then stops it only after %s is set', async (option) => { const actor = spawn(process.execPath, [path.resolve(__dirname, 'fixtures/runtime-control-daemon.cjs'), root, getDaemonSocketPath(root), 'legacy', path.resolve(__dirname, '../dist/mcp/writer-lock.js')], { stdio: ['ignore', 'ignore', 'pipe', 'ipc'] }); @@ -275,7 +276,7 @@ describe('runtime identity and readiness', () => { expect(actor.exitCode).toBeNull(); expect(fs.readFileSync(getDaemonPidPath(root), 'utf8')).toBe(before); const exited = once(actor, 'exit'); - expect(await stopRuntime(root, { legacyQuiescent: true })).toMatchObject({ pid: actor.pid, outcome: 'term' }); + expect(await stopRuntime(root, { [option]: true })).toMatchObject({ pid: actor.pid, outcome: 'term' }); await exited; const lease = reserveRuntimeWriter(root, process.pid); expect(lease).not.toBeNull(); diff --git a/site/src/content/docs/reference/api.md b/site/src/content/docs/reference/api.md index 726f70cf6..eee937e3c 100644 --- a/site/src/content/docs/reference/api.md +++ b/site/src/content/docs/reference/api.md @@ -121,10 +121,11 @@ standing down. A successful stop does not itself reserve the slot. Reserve before moving artifacts, and stand down if another writer has claimed it. By default, stop preserves a live daemon without writer coordination protocol -`1`. Set `{legacyQuiescent: true}` only after confirming all legacy MCP sessions -and direct or fallback writers for that project are stopped. Daemon death alone -does not establish that quiescence. A restored legacy daemon needs the same -explicit cutover before another promotion. +`1`. Set `{oldServersStopped: true}` only after confirming every old-version MCP +session and every direct or fallback writer for that project is stopped. Daemon +death alone does not establish that. A restored old-version daemon needs the +same explicit confirmation before another promotion. `{legacyQuiescent: true}` +is the earlier spelling of the same option. Keep the reservation value unchanged. Its generation distinguishes successive reservations held by one PID. Claim it in the daemon bootstrap, then run the diff --git a/src/runtime-control.ts b/src/runtime-control.ts index 7c5b4a2a9..6b2fa3f7a 100644 --- a/src/runtime-control.ts +++ b/src/runtime-control.ts @@ -32,18 +32,23 @@ export async function getRuntimeIdentity(projectRoot: string, options: { timeout return { pid: info.pid, version: info.version }; } -/** Refuse live uncoordinated writers unless the caller has explicitly quiesced legacy sessions. */ +/** + * Refuse live writers that predate the coordination protocol unless the caller + * confirms every such old-version server for the project has been stopped. + * `legacyQuiescent` is the earlier name for `oldServersStopped`. + */ export async function stopRuntime( projectRoot: string, - options: { legacyQuiescent?: boolean } = {}, + options: { oldServersStopped?: boolean; legacyQuiescent?: boolean } = {}, ): Promise { projectRoot = canonicalProjectRoot(projectRoot); return stopDaemonAt(projectRoot, { - preserveUnverified: true, requireWriterProtocol: !options.legacyQuiescent, + preserveUnverified: true, + requireWriterProtocol: !(options.oldServersStopped ?? options.legacyQuiescent), }); } -/** Claim a free or stale slot for a live updater, after terminal stop and legacy-session quiescence. */ +/** Claim a free or stale slot for a live updater, after a terminal stop and with every old-version server stopped. */ export function reserveRuntimeWriter(projectRoot: string, holderPid: number): WriterReservation | null { projectRoot = canonicalProjectRoot(projectRoot); const result = reserveWriterLock(projectRoot, holderPid); From 26e9e6d9d1f5ba9ab9e95b741387770e4b60f0b5 Mon Sep 17 00:00:00 2001 From: Aaron Queen Date: Sat, 10 Oct 2026 18:35:34 -0600 Subject: [PATCH 2/2] docs: exclude the daemon being stopped from the old-servers confirmation --- README.md | 2 +- site/src/content/docs/reference/api.md | 7 ++++--- src/runtime-control.ts | 3 ++- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 3635bd3d2..1bc56e074 100644 --- a/README.md +++ b/README.md @@ -866,7 +866,7 @@ reports stop outcomes, and reserves the writer slot for a live updater. A replacement bootstrap claims that reservation before running its CLI in the same process. Keep the coordination module available separately from the executable being replaced, including during rollback. Live old-version writers require explicit -confirmation that every such server is stopped before cutover. See the [runtime control contract](site/src/content/docs/reference/api.md#installer-runtime-control). +confirmation that every other such server is stopped before cutover. See the [runtime control contract](site/src/content/docs/reference/api.md#installer-runtime-control). For ordinary checkout startup, `startRuntimeWatcher(root, { expectedVersion, cliPath, runtimePath?, timeoutMs? })` reuses or elects a shared daemon without diff --git a/site/src/content/docs/reference/api.md b/site/src/content/docs/reference/api.md index eee937e3c..9929b4dff 100644 --- a/site/src/content/docs/reference/api.md +++ b/site/src/content/docs/reference/api.md @@ -121,9 +121,10 @@ standing down. A successful stop does not itself reserve the slot. Reserve before moving artifacts, and stand down if another writer has claimed it. By default, stop preserves a live daemon without writer coordination protocol -`1`. Set `{oldServersStopped: true}` only after confirming every old-version MCP -session and every direct or fallback writer for that project is stopped. Daemon -death alone does not establish that. A restored old-version daemon needs the +`1`. Set `{oldServersStopped: true}` only after confirming every other +old-version MCP session and every direct or fallback writer for that project is +stopped. The daemon serving the project is excluded because `stopRuntime` stops +it. Daemon death alone does not establish that. A restored old-version daemon needs the same explicit confirmation before another promotion. `{legacyQuiescent: true}` is the earlier spelling of the same option. diff --git a/src/runtime-control.ts b/src/runtime-control.ts index 6b2fa3f7a..1cbc7f76b 100644 --- a/src/runtime-control.ts +++ b/src/runtime-control.ts @@ -34,7 +34,8 @@ export async function getRuntimeIdentity(projectRoot: string, options: { timeout /** * Refuse live writers that predate the coordination protocol unless the caller - * confirms every such old-version server for the project has been stopped. + * confirms every other old-version server for the project has been stopped. The + * daemon this call stops is not part of that confirmation. * `legacyQuiescent` is the earlier name for `oldServersStopped`. */ export async function stopRuntime(