-
Notifications
You must be signed in to change notification settings - Fork 3.3k
feat(dev): dev server pairing and seed utilities #4557
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
t3dotgg
wants to merge
30
commits into
main
Choose a base branch
from
agent/dev-utilities
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+3,287
−119
Open
Changes from all commits
Commits
Show all changes
30 commits
Select commit
Hold shift + click to select a range
1800708
Add dev pairing and seed utilities
t3dotgg 373e791
Harden dev:seed against state it cannot settle
t3dotgg 8a76bb8
Fix liveness probe, --json output, and remaining unsettled state
t3dotgg 33e4c76
Keep the settle UPDATE within SQLite's variable ceiling
t3dotgg d3ee18c
Own the thread ceiling where the bindings are built
t3dotgg c92ad95
Delete pending turn starts, and follow the real state directory
t3dotgg dba56ba
Mirror the server's turn-id guard when settling
t3dotgg 8dee889
Fix single-column recency ordering and validate limits
t3dotgg da233f0
Break the state-dir tie on mtime, not a fixed preference
t3dotgg e7ac404
Match the base branch's bun run docs, correct the state-dir rule
t3dotgg 974d683
Document the seeder's exported surface
t3dotgg bc12b99
Re-check for a live server before commit; make the tie WAL-aware
t3dotgg 28a6971
Pin the two liveness probes to identical behavior
t3dotgg a37db1a
Give the fixture the checkpoint column its guard depends on
t3dotgg dc375f3
Cap activities by the order the app displays them
t3dotgg 9c61dbe
Cover the command-receipts delete in the fixture
t3dotgg 6045ce2
Rank activities by sequence, scope the seed guard, unify the probes
t3dotgg 14a9306
Guard the canonical database path, not the base directory
t3dotgg aad1bdd
Refuse the shared home before reporting a missing database
t3dotgg b769bb6
Resolve the database file itself; missing-target outranks same-source
t3dotgg c698e6a
Fix dev sharing across hosted and restarted servers
t3dotgg 2025191
Use dev:pair in recovery guidance
t3dotgg 79e549c
Update hosted cookie test expectation
t3dotgg e125c34
Claim dev share ownership after serving
t3dotgg df39785
Make dev share acquisition transactional
t3dotgg 9001730
Handoff dev share leases between clear and serve
t3dotgg 200774a
Restore dev sharing when lease handoff fails
t3dotgg 2187054
Keep seeded development state internally consistent
t3dotgg 6800ee3
Address dev utility review findings
t3dotgg 6588b84
Handle root seed-home containment
t3dotgg File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,161 @@ | ||
| import * as NodeServices from "@effect/platform-node/NodeServices"; | ||
| import { assert, describe, it } from "@effect/vitest"; | ||
| import * as Effect from "effect/Effect"; | ||
| import * as FileSystem from "effect/FileSystem"; | ||
| import * as Option from "effect/Option"; | ||
| import * as Path from "effect/Path"; | ||
|
|
||
| import { persistServerRuntimeState } from "../serverRuntimeState.ts"; | ||
| import { findLiveServerRuntimeState } from "./auth.ts"; | ||
|
|
||
| /** A pid that cannot belong to a live process (above the usual pid_max). */ | ||
| const DEAD_PID = 0x7ffffffe; | ||
|
|
||
| const writeRuntimeState = Effect.fn(function* (input: { | ||
| readonly baseDir: string; | ||
| readonly stateDir: "dev" | "userdata"; | ||
| readonly pid: number; | ||
| readonly port: number; | ||
| readonly devUrl?: string; | ||
| }) { | ||
| const path = yield* Path.Path; | ||
| yield* persistServerRuntimeState({ | ||
| path: path.join(input.baseDir, input.stateDir, "server-runtime.json"), | ||
| state: { | ||
| version: 1, | ||
| pid: input.pid, | ||
| port: input.port, | ||
| origin: `http://127.0.0.1:${String(input.port)}`, | ||
| ...(input.devUrl ? { devUrl: input.devUrl } : {}), | ||
| startedAt: "2026-07-20T00:00:00.000Z", | ||
| }, | ||
| }); | ||
| }); | ||
|
|
||
| const makeBaseDir = Effect.fn(function* () { | ||
| const fileSystem = yield* FileSystem.FileSystem; | ||
| const path = yield* Path.Path; | ||
| const baseDir = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3-auth-cli-test-" }); | ||
| return { | ||
| baseDir, | ||
| // What the flag heuristic resolves to without --dev-url: the userdata dir. | ||
| serverRuntimeStatePath: path.join(baseDir, "userdata", "server-runtime.json"), | ||
| }; | ||
| }); | ||
|
|
||
| describe("findLiveServerRuntimeState", () => { | ||
| it.effect("prefers the dev server when both state directories are live", () => | ||
| Effect.gen(function* () { | ||
| const path = yield* Path.Path; | ||
| const config = yield* makeBaseDir(); | ||
| // The configured path points at userdata, but this command is about dev. | ||
| yield* writeRuntimeState({ | ||
| baseDir: config.baseDir, | ||
| stateDir: "userdata", | ||
| pid: process.pid, | ||
| port: 16_601, | ||
| }); | ||
| yield* writeRuntimeState({ | ||
| baseDir: config.baseDir, | ||
| stateDir: "dev", | ||
| pid: process.pid, | ||
| port: 16_602, | ||
| devUrl: "http://localhost:8888/", | ||
| }); | ||
|
|
||
| const found = yield* findLiveServerRuntimeState(config); | ||
|
|
||
| const live = Option.getOrThrow(found); | ||
| assert.equal(live.stateDir, path.join(config.baseDir, "dev")); | ||
| assert.equal(live.state.devUrl, "http://localhost:8888/"); | ||
| }).pipe(Effect.provide(NodeServices.layer)), | ||
| ); | ||
|
|
||
| it.effect("prefers a legacy dev state without a dev URL", () => | ||
| Effect.gen(function* () { | ||
| const path = yield* Path.Path; | ||
| const config = yield* makeBaseDir(); | ||
| yield* writeRuntimeState({ | ||
| baseDir: config.baseDir, | ||
| stateDir: "userdata", | ||
| pid: process.pid, | ||
| port: 16_601, | ||
| }); | ||
| yield* writeRuntimeState({ | ||
| baseDir: config.baseDir, | ||
| stateDir: "dev", | ||
| pid: process.pid, | ||
| port: 16_602, | ||
| }); | ||
|
|
||
| const live = Option.getOrThrow(yield* findLiveServerRuntimeState(config)); | ||
| assert.equal(live.stateDir, path.join(config.baseDir, "dev")); | ||
| assert.equal(live.state.port, 16_602); | ||
| }).pipe(Effect.provide(NodeServices.layer)), | ||
| ); | ||
|
|
||
| it.effect("falls back to a live non-dev server when no dev server is running", () => | ||
| Effect.gen(function* () { | ||
| const path = yield* Path.Path; | ||
| const config = yield* makeBaseDir(); | ||
| yield* writeRuntimeState({ | ||
| baseDir: config.baseDir, | ||
| stateDir: "userdata", | ||
| pid: process.pid, | ||
| port: 16_601, | ||
| }); | ||
|
|
||
| const found = yield* findLiveServerRuntimeState(config); | ||
|
|
||
| const live = Option.getOrThrow(found); | ||
| assert.equal(live.stateDir, path.join(config.baseDir, "userdata")); | ||
| assert.equal(live.state.port, 16_601); | ||
| }).pipe(Effect.provide(NodeServices.layer)), | ||
| ); | ||
|
|
||
| // A server killed with SIGKILL never clears its state file. | ||
| it.effect("ignores state files whose process is gone", () => | ||
| Effect.gen(function* () { | ||
| const config = yield* makeBaseDir(); | ||
| yield* writeRuntimeState({ | ||
| baseDir: config.baseDir, | ||
| stateDir: "dev", | ||
| pid: DEAD_PID, | ||
| port: 16_602, | ||
| devUrl: "http://localhost:8888/", | ||
| }); | ||
|
|
||
| assert.isTrue(Option.isNone(yield* findLiveServerRuntimeState(config))); | ||
| }).pipe(Effect.provide(NodeServices.layer)), | ||
| ); | ||
|
|
||
| it.effect("skips a stale dev server in favor of a live one elsewhere", () => | ||
| Effect.gen(function* () { | ||
| const path = yield* Path.Path; | ||
| const config = yield* makeBaseDir(); | ||
| yield* writeRuntimeState({ | ||
| baseDir: config.baseDir, | ||
| stateDir: "dev", | ||
| pid: DEAD_PID, | ||
| port: 16_602, | ||
| devUrl: "http://localhost:8888/", | ||
| }); | ||
| yield* writeRuntimeState({ | ||
| baseDir: config.baseDir, | ||
| stateDir: "userdata", | ||
| pid: process.pid, | ||
| port: 16_601, | ||
| }); | ||
|
|
||
| const live = Option.getOrThrow(yield* findLiveServerRuntimeState(config)); | ||
| assert.equal(live.stateDir, path.join(config.baseDir, "userdata")); | ||
| }).pipe(Effect.provide(NodeServices.layer)), | ||
| ); | ||
|
|
||
| it.effect("reports nothing when no state files exist", () => | ||
| Effect.gen(function* () { | ||
| const config = yield* makeBaseDir(); | ||
| assert.isTrue(Option.isNone(yield* findLiveServerRuntimeState(config))); | ||
| }).pipe(Effect.provide(NodeServices.layer)), | ||
| ); | ||
| }); |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.