diff --git a/.fallowrc.json b/.fallowrc.json index 22afc8d..990e0c3 100644 --- a/.fallowrc.json +++ b/.fallowrc.json @@ -3,7 +3,7 @@ "$schema": "./node_modules/fallow/schema.json", // e2e/*.test.ts files are vitest entry points (not reached by static import from any // other source file), and e2e/fake-driver/index.ts is loaded dynamically by the - // daemon via PITLANE_DRIVERS_MODULE (see docs/CLI.md) -- neither is visible to + // daemon via SIMLOCK_DRIVERS_MODULE (see docs/CLI.md) -- neither is visible to // fallow's static reachability graph without being declared explicitly. "entry": ["src/index.ts", "src/cli/main.ts", "src/daemon/main.ts", "e2e/**/*.test.ts"], "dynamicallyLoaded": ["e2e/fake-driver/index.ts"], diff --git a/.gitignore b/.gitignore index d1297e0..844a020 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ dist/ *.log .DS_Store .fallow/ +.claude/settings.local.json diff --git a/AGENTS.md b/AGENTS.md index ddf73e1..7d4f021 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,6 +1,6 @@ # Agent guide -Pitlane is a control plane for iOS simulators and Android emulators that lets +Simlock is a control plane for iOS simulators and Android emulators that lets parallel coding agents lease devices without fighting over them. ## Rules — read before writing code diff --git a/README.md b/README.md index 4117626..7474667 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ ### A control plane so parallel coding agents stop fighting over the same simulator -Pitlane is a CLI-first control plane for iOS simulators and Android +Simlock is a CLI-first control plane for iOS simulators and Android emulators, built for environments where multiple coding agents run on one machine at the same time. Agents don't touch `simctl` or `avdmanager` -directly — they ask Pitlane for a device and get one back, booted and +directly — they ask Simlock for a device and get one back, booted and health-checked, that no other agent will touch until they're done with it. ## Why you'd want this @@ -12,10 +12,10 @@ health-checked, that no other agent will touch until they're done with it. that need a device grab whatever `simctl` / `avdmanager` happens to show them. Two agents that pick the same one start booting, erasing, and installing over each other — without either ever knowing the other exists. -Pitlane gives them a single primitive instead: **lease a device**. +Simlock gives them a single primitive instead: **lease a device**. **You don't provision devices by hand.** If nothing matching is free, -Pitlane provisions one itself, up to a capacity limit it derives from the +Simlock provisions one itself, up to a capacity limit it derives from the machine's CPU and RAM. Once that limit is reached, further requests block and wait in a fair queue rather than failing outright, with `--timeout` and `--no-wait` escape hatches for callers that want different behavior. @@ -25,15 +25,15 @@ using is shut down after a short idle period to reclaim RAM, then deleted after a longer one to reclaim disk — automatically, in tiers. **A crashed simulator doesn't just quietly cost you a device.** If a leased -device's process dies outside pitlane, Pitlane notices, reboots it under the +device's process dies outside simlock, Simlock notices, reboots it under the same lease, and tells the holder — it can't restore whatever was running inside the device when it died, but the lease and its device don't just vanish. -**It's advisory, not a sandbox.** Pitlane doesn't wrap or intercept +**It's advisory, not a sandbox.** Simlock doesn't wrap or intercept `simctl` / `avdmanager` — it works because agents are instructed to only use devices handed to them by a lease. What it _does_ enforce is its own blast -radius: Pitlane only ever shuts down, erases, or deletes devices it created +radius: Simlock only ever shuts down, erases, or deletes devices it created itself. Everything else on the machine is read-only to it. **Built for agents first, humans second.** Lease results are one JSON line @@ -45,7 +45,7 @@ form instead. ## What it looks like ```sh -pitlane lease --platform ios --device "iPhone 16" --detach +simlock lease --platform ios --device "iPhone 16" --detach ``` ```json @@ -64,7 +64,7 @@ back an identified, ready-to-use device. Release it explicitly, or let its TTL expire. Now say a second agent asks for the same `iPhone 16` a moment later. It's -already leased to the first agent — no problem, Pitlane just provisions +already leased to the first agent — no problem, Simlock just provisions another one. Progress streams as JSON lines on stderr while it happens, and the lease result lands on stdout the moment the new device is ready: @@ -97,10 +97,10 @@ get the same lease/release workflow as tools, through a local stdio server: ```json { "mcpServers": { - "pitlane": { - "command": "pitlane", + "simlock": { + "command": "simlock", "args": ["mcp"], - "env": { "PITLANE_AGENT_ID": "agent-1" } + "env": { "SIMLOCK_AGENT_ID": "agent-1" } } } } @@ -111,14 +111,14 @@ get the same lease/release workflow as tools, through a local stdio server: ```sh pnpm install pnpm build -pitlane lease --platform ios --device "iPhone 16" --detach -pitlane status --json +simlock lease --platform ios --device "iPhone 16" --detach +simlock status --json ``` The daemon starts on demand — there's no separate setup step. Use -`pitlane doctor` to reconcile managed state with reality, and -`pitlane nuke --yes --delete-devices` only for an emergency reset of -Pitlane-managed devices. +`simlock doctor` to reconcile managed state with reality, and +`simlock nuke --yes --delete-devices` only for an emergency reset of +Simlock-managed devices. See [docs/CLI.md](docs/CLI.md) for the full command reference and [docs/CLI.md#mcp-integration-optional](docs/CLI.md) or the [README section @@ -126,13 +126,13 @@ below](#mcp-integration-optional) for wiring up an MCP client. ## MCP integration (optional) -The CLI remains Pitlane's primary, full operator interface. MCP is a +The CLI remains Simlock's primary, full operator interface. MCP is a narrower, agent-focused integration: it intentionally exposes neither status, configuration, events, lease renewal, nor destructive or other -operator commands. Start it with `pitlane mcp` — it reserves stdout for MCP +operator commands. Start it with `simlock mcp` — it reserves stdout for MCP JSON-RPC, so lease results never mix with protocol framing. -`PITLANE_AGENT_ID` sets the server's stable requester identity. Pitlane +`SIMLOCK_AGENT_ID` sets the server's stable requester identity. Simlock allows at most one active lease per identity, so give each agent session a distinct, stable id — run one MCP server process per agent session, each with its own id. @@ -142,7 +142,7 @@ what can be leased), `lease_simulator`, `release_simulator`, and `lease_status` (cheap, safe to poll after a context compaction to check whether a device is still held). Full tool contracts, progress reporting, and lease-loss notifications are documented in -[docs/CLI.md](docs/CLI.md#pitlane-mcp). +[docs/CLI.md](docs/CLI.md#simlock-mcp). ## Documentation @@ -150,13 +150,13 @@ and lease-loss notifications are documented in - [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) — how the daemon, drivers, and frontends fit together - [docs/CLI.md](docs/CLI.md) — the full command reference - [docs/CONFIGURATION.md](docs/CONFIGURATION.md) — every config key, its default, and how limits interact -- [docs/EVENTS.md](docs/EVENTS.md) — catalog of business events on `pitlane events` +- [docs/EVENTS.md](docs/EVENTS.md) — catalog of business events on `simlock events` - [docs/known-pitfalls.md](docs/known-pitfalls.md) — accepted gaps and their planned fixes - [docs/IDEAS.md](docs/IDEAS.md) — post-v1 ideas, not yet built ## Made with ❤️ at Callstack -`pitlane` is an open source project and will always remain free to use. If +`simlock` is an open source project and will always remain free to use. If you think it's cool, please star it 🌟. [Callstack][callstack-readme-with-love] is a group of React and React Native geeks, contact us at [hello@callstack.com](mailto:hello@callstack.com) if you need any help with @@ -164,4 +164,4 @@ these or just want to say hi! Like the project? ⛸️ [Join the team](https://callstack.com/careers/?utm_campaign=Senior_RN&utm_source=github&utm_medium=readme) who does amazing stuff for clients and drives React Native Open Source! 🔥 -[callstack-readme-with-love]: https://callstack.com/?utm_source=github.com&utm_medium=referral&utm_campaign=pitlane&utm_term=readme-with-love +[callstack-readme-with-love]: https://callstack.com/?utm_source=github.com&utm_medium=referral&utm_campaign=simlock&utm_term=readme-with-love diff --git a/docs/ABOUT.md b/docs/ABOUT.md index 5e97dd7..26eae60 100644 --- a/docs/ABOUT.md +++ b/docs/ABOUT.md @@ -1,6 +1,6 @@ -# Pitlane +# Simlock -Pitlane is a control plane for iOS simulators and Android emulators, built for +Simlock is a control plane for iOS simulators and Android emulators, built for environments where multiple coding agents run in parallel on one machine. ## The problem @@ -11,15 +11,15 @@ and installing over each other — without ever knowing the other exists. ## The solution -Pitlane is a CLI-first control plane (backed by a local daemon) that is the +Simlock is a CLI-first control plane (backed by a local daemon) that is the same for both platforms and gives agents one primitive: **lease a device**. An optional local stdio MCP integration exposes the focused lease/release workflow to compatible agent clients; the CLI remains the full operator interface. -- `pitlane lease` returns a *ready* device — booted and health-checked — that +- `simlock lease` returns a *ready* device — booted and health-checked — that no other agent will touch for the duration of the lease. -- If no matching device is free, pitlane **provisions** one, up to a +- If no matching device is free, simlock **provisions** one, up to a configurable capacity limit derived from the machine's CPU and RAM. - If the limit is reached, the CLI **blocks and waits** in a fair queue until a device frees up (with `--timeout` and `--no-wait` escape hatches). @@ -28,14 +28,14 @@ interface. ## Key properties -- **Advisory coordination.** Pitlane does not sandbox anything. It works +- **Advisory coordination.** Simlock does not sandbox anything. It works because agents are instructed to never call `simctl` / `avdmanager` directly and to only use devices handed to them by a lease. - **Process-held leases.** The `lease` command stays running in the background; the open connection to the daemon is the heartbeat. Killing the process releases the lease. A daemon-side TTL is the backstop for zombies. - **One lease per agent** (v1). -- **Managed-device registry.** Pitlane only ever shuts down, erases, or +- **Managed-device registry.** Simlock only ever shuts down, erases, or deletes devices it created itself. Everything else on the machine is read-only to it. - **Agent-first output.** CLI lease results are one JSON line on stdout; diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index adba140..7f60bbe 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -3,8 +3,8 @@ ## Topology ``` -agent ──spawns──> pitlane CLI ──┐ - ├─ shared daemon client ──unix socket──> pitlane daemon +agent ──spawns──> simlock CLI ──┐ + ├─ shared daemon client ──unix socket──> simlock daemon MCP client ──spawns──> stdio MCP ┘ │ ┌───┼─────────────┐ │ core (platform-│ @@ -76,7 +76,7 @@ reclaim(device) -> ready | shutdown // fresh-state strategy lives here shutdown(device) destroy(device) estimate(op) -> ETA for progress events -listManaged() -> Pitlane-prefixed device/process reality for doctor +listManaged() -> Simlock-prefixed device/process reality for doctor ``` The litmus test for the boundary: adding a third driver (e.g. physical @@ -138,7 +138,7 @@ provisioning → ready → leased → reclaiming → ready/shutdown → deleted ready/shutdown/deleted ``` -All transitions go through the core. `pitlane status` reads identically for +All transitions go through the core. `simlock status` reads identically for iOS and Android because of this. A warm device is derived inventory, not a state: any registry-managed, @@ -160,7 +160,7 @@ against capacity, not grantable" is expressed by adding its own entry into `quarantined`, not by inventing a second state: the release-time purge failure (`reclaiming → quarantined`) and the stalled-transition timeout (`provisioning → quarantined`, both owned by `QuarantineCoordinator`) are its -two entries. The latter fires from `pitlane doctor`'s `stalled-transition` +two entries. The latter fires from `simlock doctor`'s `stalled-transition` finding — a `provisioning`/`reclaiming` device whose time in that state has outrun a driver-derived threshold, meaning the driver call meant to resolve it never did and the registry's view has diverged from the driver's. Safer @@ -212,11 +212,11 @@ Conclusions baked into the drivers: connection. For the CLI, that is the CLI process; for MCP, it is the MCP server process for that agent session. Connection close = release. The CLI holder additionally watches its parent through the `ParentWatch` port and - self-terminates if it dies, so a crashed agent's backgrounded `pitlane + self-terminates if it dies, so a crashed agent's backgrounded `simlock lease` cannot outlive it by getting reparented — see [known-pitfalls.md](known-pitfalls.md). - **Detached mode (`--detach`)**: returns a token, daemon enforces a TTL, the - agent must `pitlane renew` periodically. + agent must `simlock renew` periodically. - **TTL backstop**: even held leases have a long daemon-side TTL for zombie sockets, machine sleep, etc. - **Heartbeat-driven sliding TTL, capability-gated**: a held lease's backstop @@ -260,7 +260,7 @@ The device is not lost track of while that runs. It is `reclaiming`, so it still counts as running capacity and is invisible to every grant path (`AcquisitionPlanner` selects by exact state), and the reclaim holds a `reclaim` operation claim for its whole duration — which is how -`StartupConverger#recoverInterruptedReclaims` and `pitlane doctor`'s +`StartupConverger#recoverInterruptedReclaims` and `simlock doctor`'s stalled-transition finding both tell a live purge from an abandoned one. A waiter queued for exactly that device is granted the moment the purge settles: the coordinator re-notifies acquisition *after* releasing the claim, because @@ -273,7 +273,7 @@ Three things still wait for the purge, deliberately: records, so a device left mid-reclaim would be skipped by the very reset meant to take it down. `beginMaintenance` drains in-flight background reclaims, and the maintenance-authorized release awaits its own inline. -- **A graceful `pitlane daemon stop`.** It drains the in-flight reclaims +- **A graceful `simlock daemon stop`.** It drains the in-flight reclaims (before disposing timers, so a purge that settles into quarantine still gets its retry cancelled), leaving the pool in the same settled shape an inline reclaim used to. @@ -326,10 +326,10 @@ nuke interfaces rather than duplicating core decisions in the CLI or server. `Doctor.reconcile()` already knew a leased device could crash: its `expectedRunState` maps `leased -> "running"`, so a leased device whose -process an operator kills from outside pitlane produces a +process an operator kills from outside simlock produces a `foreign-state-change` finding. What was missing was anything that acted on that finding at the moment it mattered. `reconcile()` only ran at daemon -startup and from an explicit `pitlane doctor`, so a crash between those +startup and from an explicit `simlock doctor`, so a crash between those points sat undetected indefinitely. And even a `doctor --fix` run that saw it couldn't repair it: `#fixForeignStateChange` bails on a leased device, the cleanup reaper filters leased targets centrally before a rule ever runs, and @@ -390,7 +390,7 @@ should. None of this is silent. A reboot resumes the lease, but it cannot resume whatever the agent had running *inside* the device when it died — a launched -app, a `log stream`, an Appium/XCUITest session, a port forward — pitlane has +app, a `log stream`, an Appium/XCUITest session, a port forward — simlock has no way to know that state existed, let alone restore it. So the monitor emits `device.crash-detected` the moment a crash is confirmed and `device.recovered` once the reboot passes readiness; the daemon pushes both to whichever @@ -434,14 +434,14 @@ Reaper triggers are observer subscriptions to `lease.released`, reaper itself emits `disk.pressure-detected` (edge-triggered, once per crossing) as a post-commit fact for observers — never as the mechanism that drives `idle-destroy`'s own behavior. Every successful action emits its rule -and reason in `cleanup.executed`; `pitlane cleanup --dry-run` previews +and reason in `cleanup.executed`; `simlock cleanup --dry-run` previews proposals. ## Event bus An in-process, typed event bus carries **past-tense business facts** (`device.reclaimed`, `lease.expired`). Observers — cleanup triggers, -logging/metrics, `pitlane events --follow` — subscribe to it. Warm-pool +logging/metrics, `simlock events --follow` — subscribe to it. Warm-pool reclaim/disposition, cleanup execution, startup convergence, eviction, and nuke remain explicit direct component call chains. @@ -534,11 +534,11 @@ destruction, never touching a leased device) is unsafe to have in flight during shutdown; it just means "stopped" is not instantaneous relative to the failure being reported. `health` itself does not grow a third state for this: `running` means convergence finished, not that every backgrounded reclaim it -kicked off has settled — `pitlane status` already reports each device's own +kicked off has settled — `simlock status` already reports each device's own state (`reclaiming` included), so a separate aggregate would duplicate information already visible per-device rather than add any. -Operational logging is a separate concern from the event bus: `pitlane events` +Operational logging is a separate concern from the event bus: `simlock events` carries business facts (lease granted, device cleaned up, …) in an in-memory ring buffer that resets on restart, while the `Logger` port writes durable, structured JSON lines — one per record — for startup, socket claim/recovery, @@ -549,7 +549,7 @@ module-scoped children (`logger.child("server")`, `.child("connection-host")`, `.child("driver-discovery")`) to each component so every line is attributable. The sink tracks bytes written and rotates `daemon.log` to `daemon.log.1` (replacing any previous generation) once `config.log.rotateBytes` is exceeded, -so growth is bounded and `pitlane daemon logs` reads the rotated generation +so growth is bounded and `simlock daemon logs` reads the rotated generation before the current file. The one exception is the fatal top-level handler: it cannot depend on `config.log` having loaded successfully, so it builds its own logger straight from the default log path at a fixed level, falling back to diff --git a/docs/CLI.md b/docs/CLI.md index c4f298d..c8dbf6f 100644 --- a/docs/CLI.md +++ b/docs/CLI.md @@ -1,6 +1,6 @@ # CLI reference -Part of the user manual: every command the pitlane CLI is expected to +Part of the user manual: every command the simlock CLI is expected to implement. Results are JSON on **stdout**; progress/diagnostics are JSON lines on **stderr** — this is the default output, not an opt-in, because agents are the primary audience. `status`, `catalog`, and @@ -8,7 +8,7 @@ agents are the primary audience. `status`, `catalog`, and human-oriented view for interactive/operator use and accept `--json` to switch to the structured form. Every other command's output is already unconditionally JSON, so passing `--json` to it is a usage error (exit 2) -rather than a silent no-op. `pitlane mcp` reserves stdout for MCP JSON-RPC +rather than a silent no-op. `simlock mcp` reserves stdout for MCP JSON-RPC framing and accepts no flags at all. On failure, every command writes one structured line to stderr: @@ -21,7 +21,7 @@ On failure, every command writes one structured line to stderr: daemon, or a stable CLI-level code otherwise: `USAGE` for bad flags/missing arguments/unknown commands, `INTERNAL` for anything unexpected. An unknown command or a missing required argument gets a `message` that ends with a -pointer to `pitlane --help`, so a human hitting one from a terminal isn't +pointer to `simlock --help`, so a human hitting one from a terminal isn't stranded with only a JSON blob — the full command banner itself is no longer dumped to stderr on every failure, only on request via `--help`. @@ -59,23 +59,23 @@ like a different requester. Resolution order, first match wins: -1. `--agent-id ` on `pitlane lease`. -2. the `PITLANE_AGENT_ID` environment variable. +1. `--agent-id ` on `simlock lease`. +2. the `SIMLOCK_AGENT_ID` environment variable. 3. a pid-derived value (today's behavior; not stable across invocations). Reuse the same id across an agent's own invocations (e.g. export -`PITLANE_AGENT_ID` once per agent session) and use a distinct id per agent so +`SIMLOCK_AGENT_ID` once per agent session) and use a distinct id per agent so they don't collide with each other. The id shows up as the requester in -`pitlane status` and `pitlane list --leases`, so an operator can tell which +`simlock status` and `simlock list --leases`, so an operator can tell which agent holds what. -## `pitlane lease` +## `simlock lease` Acquire a device. Blocks while waiting for capacity, then while provisioning and booting, then — in held mode — keeps running to hold the lease. ``` -pitlane lease --platform --device [--os ] +simlock lease --platform --device [--os ] [--agent-id ] [--timeout ] [--no-wait] [--detach] [--allow-download] [--bind-pid ] ``` @@ -83,7 +83,7 @@ pitlane lease --platform --device [--os ] - `--platform`, `--device` — required. `--os` defaults to the newest runtime already installed for that platform. - `--agent-id` — this invocation's requester identity; see - [Agent identity](#agent-identity). Defaults to `PITLANE_AGENT_ID`, then a + [Agent identity](#agent-identity). Defaults to `SIMLOCK_AGENT_ID`, then a pid-derived value. - `--timeout` — max time to wait in the queue (exit 10 on expiry). - `--no-wait` — fail immediately with exit 11 instead of queueing. @@ -92,7 +92,7 @@ pitlane lease --platform --device [--os ] iOS runtimes remain Xcode-managed in v1: `--allow-download` cannot install them; install the runtime through Xcode first. - `--detach` — detached mode: print the lease result and exit; the lease is - TTL-bound and must be renewed with `pitlane lease renew`. + TTL-bound and must be renewed with `simlock lease renew`. - `--bind-pid ` — held mode only: watch this pid for death instead of the CLI's actual parent. For a holder spawned from a short-lived subshell, the immediate parent can die (and get reaped) while the owning agent is @@ -133,7 +133,7 @@ stream: {"event":"device_recovered","lease":"lse_9f2c","device_id":"dev_1a2b","attempts":1} ``` -`device_unhealthy` means the device stopped running outside pitlane and a +`device_unhealthy` means the device stopped running outside simlock and a reboot is in progress under the same lease; `device_recovered` means that reboot passed readiness. The lease itself is untouched by either — it is still held and must still be released the normal way. Recovery can instead @@ -146,7 +146,7 @@ lease, which surfaces as the same line any other lease loss does: ``` In all three lines `device_id` is the registry device id — the `id` column of -`pitlane list --devices`, and the same identifier the event bus uses — not the +`simlock list --devices`, and the same identifier the event bus uses — not the driver-level `udid` the grant returns on stdout. A `lease_lost` line is terminal for held mode: there is no longer a lease to hold, so the process writes that line and exits `14` rather than waiting for a @@ -157,7 +157,7 @@ for what a reboot cannot bring back — anything the agent had running inside the device (a launched app, `log stream`, an Appium/XCUITest session, a port forward) is gone whether or not recovery succeeds. -### `pitlane lease renew [--ttl ]` +### `simlock lease renew [--ttl ]` Extend a lease's TTL — works for both detached and held-mode leases. Renewal always resets the deadline to now plus the TTL, regardless of how much time @@ -175,7 +175,7 @@ further out than that does not stick — the next heartbeat pulls it back in. Hand-renewal remains the only keep-alive for detached mode, which by design never holds a connection to heartbeat over. -## `pitlane release | --all` +## `simlock release | --all` Explicitly release a lease (primarily for detached mode or operator intervention). `--all` force-releases every lease — confirmation required @@ -193,14 +193,14 @@ What that means for the next command: the device is `reclaiming` for a moment after `release` returns, so it still counts as running capacity and is not grantable yet. A `lease` request that wants it simply queues and is granted the instant the purge finishes; nothing is lost, but `status` right after a release -will show `reclaiming` rather than `ready`. `pitlane daemon stop` waits for +will show `reclaiming` rather than `ready`. `simlock daemon stop` waits for in-flight purges before exiting, so a graceful shutdown still leaves the pool settled; a daemon killed mid-purge leaves its devices `reclaiming` for the next startup to recover. -## `pitlane mcp` +## `simlock mcp` -Start Pitlane's local stdio MCP server. It accepts no flags. Standard output +Start Simlock's local stdio MCP server. It accepts no flags. Standard output is reserved for MCP JSON-RPC; fatal diagnostics are written to stderr. The server auto-starts the daemon when needed and exposes the focused `list_devices`, `lease_simulator`, `release_simulator`, and `lease_status` @@ -212,19 +212,19 @@ for that request. See [../README.md](../README.md#mcp-integration-optional) for details. The requester identity for leases made through this server is -`PITLANE_AGENT_ID`, falling back to a pid-derived value — see -[Agent identity](#agent-identity). Set a distinct `PITLANE_AGENT_ID` per MCP +`SIMLOCK_AGENT_ID`, falling back to a pid-derived value — see +[Agent identity](#agent-identity). Set a distinct `SIMLOCK_AGENT_ID` per MCP server process (one per agent session) so the one-lease-per-agent rule is meaningful. -## `pitlane status` +## `simlock status` Human and JSON status include derived warm counts globally and per platform. `ready` devices contribute to those counts; `reclaiming` and `quarantined` devices remain visible as busy running capacity and never contribute to warm inventory. A `quarantined` device is one whose release-time purge failed, or whose `provisioning`/`reclaiming` transition stalled past its driver-derived -threshold (see `pitlane doctor` below): it stays visible in `status` and +threshold (see `simlock doctor` below): it stays visible in `status` and `list --devices` with that state while `QuarantineCoordinator` retries it in the background, and is never handed to a new requester. @@ -241,13 +241,13 @@ for the structured equivalent. `overLimit` is true when a lowered limit cannot yet be met, for example because active leases consume all running slots. -## `pitlane list [--devices|--leases|--rules]` +## `simlock list [--devices|--leases|--rules]` Scriptable listings of managed devices, active leases, or registered cleanup rules. Defaults to `--devices`. Each lease record's `requesterId` is the agent id (see [Agent identity](#agent-identity)) that holds it. -## `pitlane catalog [--platform ] [--json]` +## `simlock catalog [--platform ] [--json]` Lists what can actually be leased, so an agent can pick a valid `--device` and `--os` without a failed round trip through `lease`. For each available @@ -265,22 +265,22 @@ structured equivalent: {"platforms":[{"platform":"ios","models":["iPhone 17 Pro","iPhone 16"],"runtimes":["18.4","26.5"],"defaultRuntime":"26.5"}]} ``` -## `pitlane cleanup [--dry-run] [--rule ]` +## `simlock cleanup [--dry-run] [--rule ]` Run the cleanup reconciliation immediately. `--dry-run` prints the actions each rule *would* take (rule name, target, reason) without executing. `--rule` restricts to a single named rule (e.g. `--rule idle-destroy`); see -`pitlane list --rules` for the registered rules. +`simlock list --rules` for the registered rules. -## `pitlane doctor [--fix]` +## `simlock doctor [--fix]` Reconcile the daemon's state with reality (`simctl list`, `adb devices`, running emulator processes): report orphaned processes, registry entries -whose device vanished, devices booted outside pitlane, expired-but-held +whose device vanished, devices booted outside simlock, expired-but-held leases, and devices stuck mid-transition. `--fix` applies the safe corrections. -A `provisioning` or `reclaiming` device is normally in-flight work Pitlane +A `provisioning` or `reclaiming` device is normally in-flight work Simlock itself is driving and is not reported — but only up to a driver-derived threshold (`Driver.estimate` for that operation, scaled by `stalledTransition.thresholdMultiplier` and floored at @@ -289,27 +289,27 @@ Past that threshold it becomes a `stalled-transition` finding: the driver call that was supposed to resolve the transition never did, and the registry's view of the device has diverged from the driver's. `--fix` responds the same way it does for a release-time purge failure — the device -enters `quarantined` (see [#21](https://github.com/callstackincubator/pitlane/issues/21)) +enters `quarantined` (see [#21](https://github.com/callstackincubator/simlock/issues/21)) rather than being re-driven, since it may be mid-erase. As with every other `--fix` correction, a leased device is never touched. -## `pitlane nuke [--delete-devices] [--yes]` +## `simlock nuke [--delete-devices] [--yes]` Emergency reset: force-release all leases, kill emulator/simulator processes -pitlane started, clear the queue. With `--delete-devices`, also destroy every +simlock started, clear the queue. With `--delete-devices`, also destroy every registry-managed device. Never touches devices outside the registry. -## `pitlane events [--follow] [--since ]` +## `simlock events [--follow] [--since ]` Stream the business-event ring buffer (see [EVENTS.md](EVENTS.md)) as JSON lines. `--follow` keeps streaming; `--since 1h` replays recent history. -## `pitlane daemon ` +## `simlock daemon ` Manage the daemon explicitly. Other commands auto-start it on demand; `daemon` exists for operators and debugging. `logs` tails daemon logs. -The daemon writes one structured JSON line per record to `~/.pitlane/daemon.log` +The daemon writes one structured JSON line per record to `~/.simlock/daemon.log` (timestamp, level, module, message, and any fields) covering startup (version, protocol version, socket path, effective config), socket claim/stale-endpoint recovery, driver discovery, connection open/close, shutdown, and unexpected or @@ -317,7 +317,7 @@ handled errors. Growth is bounded: once the file passes `log.rotateBytes` it is rotated to `daemon.log.1` (replacing any previous generation), so `logs` always shows the current file with the immediately preceding one prepended. -## `pitlane config [get |set ]` +## `simlock config [get |set ]` Show the effective configuration (defaults + config file + overrides): managed and running capacity limits, idle tiers T1/T2/T3, TTLs, disk-pressure @@ -328,18 +328,18 @@ driver; both must have room before provisioning or booting a shutdown device. ## Environment variables -### `PITLANE_HOME` +### `SIMLOCK_HOME` Overrides the data directory the CLI, MCP server, and daemon all use for `config.json`, `state.json`, `daemon.sock`, and `daemon.log`. Defaults to -`~/.pitlane`. All three frontends resolve it through the same function -(`resolvePitlaneHome` in `src/ports/paths.ts`), so setting it once in an +`~/.simlock`. All three frontends resolve it through the same function +(`resolveSimlockHome` in `src/ports/paths.ts`), so setting it once in an agent's environment repoints every command at an isolated data directory — -useful for running multiple independent pitlane instances on one machine, or +useful for running multiple independent simlock instances on one machine, or for tests. When the CLI or MCP server auto-starts the daemon, the daemon process inherits the variable like the rest of the environment. -### `PITLANE_DRIVERS_MODULE` (advanced / testing hook) +### `SIMLOCK_DRIVERS_MODULE` (advanced / testing hook) Overrides driver discovery (`discoverDrivers` in `src/daemon/main.ts`) with a JavaScript module of your own instead of the real iOS/Android drivers. Point diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index 2483069..984af85 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -1,37 +1,37 @@ # Configuration -Pitlane reads `~/.pitlane/config.json` and merges it over built-in +Simlock reads `~/.simlock/config.json` and merges it over built-in defaults. Only the keys below are recognized; unknown keys are ignored with a warning. Inspect the effective, merged configuration at any time with -`pitlane config`. +`simlock config`. | Property | Description | Default | | ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- | | `limits.maxRunning` | Global cap on devices running at once, across both platforms. | Sum of `limits.ios.maxDevices` and `limits.android.maxDevices` | -| `limits.ios.maxDevices` | Max number of iOS simulators Pitlane will manage at once. | `max(1, cpuCount / 2)` | +| `limits.ios.maxDevices` | Max number of iOS simulators Simlock will manage at once. | `max(1, cpuCount / 2)` | | `limits.ios.maxRunning` | Max number of iOS simulators running at once. | Same as `limits.ios.maxDevices` | -| `limits.android.maxDevices` | Max number of Android emulators Pitlane will manage at once. | `max(1, min(cpuCount / 4, totalRamGb / 8))` | +| `limits.android.maxDevices` | Max number of Android emulators Simlock will manage at once. | `max(1, min(cpuCount / 4, totalRamGb / 8))` | | `limits.android.maxRunning` | Max number of Android emulators running at once. | Same as `limits.android.maxDevices` | | `ramBudget.iosBytesPerDevice` | RAM reserved per iOS simulator when computing capacity. | `1.5 GiB` | | `ramBudget.androidBytesPerDevice` | RAM reserved per Android emulator when computing capacity. | `4 GiB` | -| `idle.shutdownAfterMs` | How long an unused device sits idle before Pitlane shuts it down (tier 1, reclaims RAM). | `10 minutes` | -| `idle.deleteAfterMs` | How long a shut-down device sits idle before Pitlane deletes it (tier 2, reclaims disk). | `1 hour` | -| `warmPool.quarantine.maxRetries` | Failed purge retries allowed on a quarantined device (after the triggering failure) before Pitlane gives up and destroys it. | `3` | +| `idle.shutdownAfterMs` | How long an unused device sits idle before Simlock shuts it down (tier 1, reclaims RAM). | `10 minutes` | +| `idle.deleteAfterMs` | How long a shut-down device sits idle before Simlock deletes it (tier 2, reclaims disk). | `1 hour` | +| `warmPool.quarantine.maxRetries` | Failed purge retries allowed on a quarantined device (after the triggering failure) before Simlock gives up and destroys it. | `3` | | `warmPool.quarantine.retryBackoffMs` | Delay before the first quarantine purge retry. | `30 seconds` | | `warmPool.quarantine.retryBackoffMultiplier` | Growth factor applied to the backoff after each failed retry. | `2` | | `warmPool.quarantine.maxRetryBackoffMs` | Cap on the quarantine retry backoff. | `5 minutes` | | `lease.heldTtlBackstopMs` | Backstop TTL for held-mode leases, in case the holding process dies without releasing. | `1 hour` | -| `lease.detachedTtlMs` | TTL for detached-mode leases before they must be renewed with `pitlane lease renew`. | `15 minutes` | +| `lease.detachedTtlMs` | TTL for detached-mode leases before they must be renewed with `simlock lease renew`. | `15 minutes` | | `lease.heartbeatIntervalMs` | How often the daemon pings a held-mode connection that declared the `heartbeat` capability; each pong slides that connection's leases' TTL back out to a full `heldTtlBackstopMs`. Must be `<= lease.heldTtlBackstopMs / 4`. | `5 minutes` | -| `diskPressure.freeBytesThreshold` | Free disk space below which Pitlane treats the machine as under disk pressure. | `10 GiB` | -| `eventBuffer.capacity` | Number of business events kept in the in-memory ring buffer (see `pitlane events`). | `1000` | +| `diskPressure.freeBytesThreshold` | Free disk space below which Simlock treats the machine as under disk pressure. | `10 GiB` | +| `eventBuffer.capacity` | Number of business events kept in the in-memory ring buffer (see `simlock events`). | `1000` | | `health.enabled` | Master switch for leased-device crash detection and recovery. | `true` | | `health.probeIntervalMs` | How often the health monitor observes leased devices against driver reality. | `30 seconds` | | `health.stableObservations` | Consecutive `stopped` observations required before a leased device is treated as crashed; guards against transient `Booting`/`Shutting Down`/adb-offline readings. | `2` | | `health.maxRecoveryAttempts` | Reboot attempts for one lease before the lease is given up as lost. | `3` | | `health.recoveryBackoffMs` | Base delay between reboot attempts; the monitor applies exponential backoff over it. | `5 seconds` | | `health.maxConcurrentRecoveries` | Cap on simultaneous recovery reboots, so a machine wake (every device reads `stopped` at once) cannot start a boot storm. | `1` | -| `stalledTransition.thresholdMultiplier` | Factor applied to a driver's own `provision + boot` (for `provisioning`) or `reclaim` (for `reclaiming`) estimate to get the stall threshold for `pitlane doctor`'s `stalled-transition` finding. | `3` | +| `stalledTransition.thresholdMultiplier` | Factor applied to a driver's own `provision + boot` (for `provisioning`) or `reclaim` (for `reclaiming`) estimate to get the stall threshold for `simlock doctor`'s `stalled-transition` finding. | `3` | | `stalledTransition.minimumThresholdMs` | Floor under the multiplied estimate, for a driver whose estimate is near zero. | `1 minute` | All limit values must be positive integers; all durations and byte sizes @@ -56,5 +56,5 @@ global level, to their sum): } ``` -See [CLI.md](CLI.md#pitlane-config-get-keyset-key-value) for the -`pitlane config` command itself. +See [CLI.md](CLI.md#simlock-config-get-keyset-key-value) for the +`simlock config` command itself. diff --git a/docs/EVENTS.md b/docs/EVENTS.md index bfb73c2..643d89d 100644 --- a/docs/EVENTS.md +++ b/docs/EVENTS.md @@ -14,9 +14,9 @@ in short: `subject.past-tense-fact`, emitted post-commit, facts not commands. | `lease.requested` | request spec, requester, wait policy | a lease request is accepted by the daemon | LeaseAcquisitionCoordinator | implemented | | `lease.queued` | request id, queue position | no capacity; request entered the wait queue | LeaseAcquisitionCoordinator | implemented | | `lease.granted` | lease id, device id, requester, mode (held/detached) | a device was assigned and handed out | LeaseLifecycle | implemented | -| `lease.renewed` | lease id, new deadline | an explicit `pitlane lease renew` succeeded (either mode), **or** a held-mode connection that declared the `heartbeat` capability answered a `lease.heartbeat` push (fires once per lease per `lease.heartbeatIntervalMs` while the holder stays alive) | LeaseLifecycle | implemented | -| `lease.released` | lease id, device id, reason (closed/explicit/killed/orphaned/device-lost) | holder connection closed, explicit release, (orphaned) a `held` lease found still persisted at daemon startup, which cannot have a live holder across a restart, or (device-lost) a leased device could not be recovered after it stopped running outside pitlane | LeaseLifecycle | implemented | -| `lease.expired` | lease id, device id | TTL backstop fired without a heartbeat sliding it first — for a capability-declaring holder this means it stopped ponging (crashed, hung, or lost its socket); for one that never declared the capability it means the grant-time TTL (or the last explicit `pitlane lease renew`) simply ran out, exactly as before this change | LeaseLifecycle | implemented | +| `lease.renewed` | lease id, new deadline | an explicit `simlock lease renew` succeeded (either mode), **or** a held-mode connection that declared the `heartbeat` capability answered a `lease.heartbeat` push (fires once per lease per `lease.heartbeatIntervalMs` while the holder stays alive) | LeaseLifecycle | implemented | +| `lease.released` | lease id, device id, reason (closed/explicit/killed/orphaned/device-lost) | holder connection closed, explicit release, (orphaned) a `held` lease found still persisted at daemon startup, which cannot have a live holder across a restart, or (device-lost) a leased device could not be recovered after it stopped running outside simlock | LeaseLifecycle | implemented | +| `lease.expired` | lease id, device id | TTL backstop fired without a heartbeat sliding it first — for a capability-declaring holder this means it stopped ponging (crashed, hung, or lost its socket); for one that never declared the capability it means the grant-time TTL (or the last explicit `simlock lease renew`) simply ran out, exactly as before this change | LeaseLifecycle | implemented | | `lease.rejected` | request spec, reason (timeout/no-wait/unresolvable-spec/already-leased/boot-timeout/killed) | a request ended without a grant | LeaseAcquisitionCoordinator / WaitQueue | implemented | ## Device lifecycle @@ -34,7 +34,7 @@ in short: `subject.past-tense-fact`, emitted post-commit, facts not commands. | `device.shutdown` | device id, initiator (rule/command) | device stopped, still on disk | Registry; WarmPoolCoordinator for interrupted reclaim recovery | implemented | | `device.deleted` | device id, initiator | device removed from disk and registry | Registry | implemented | | `device.foreign-state-detected` | device id, platform, expected (running/stopped), observed (running/stopped) | doctor reconcile found a managed device's observed boot state disagreeing with the committed registry state | Doctor | implemented | -| `device.foreign-provenance-detected` | device id, platform, detail (erased/mark-mismatch/durable-mark-missing) | doctor reconcile found a managed device's provenance marks no longer proving Pitlane owns it | Doctor | implemented | +| `device.foreign-provenance-detected` | device id, platform, detail (erased/mark-mismatch/durable-mark-missing) | doctor reconcile found a managed device's provenance marks no longer proving Simlock owns it | Doctor | implemented | | `device.stalled-transition-detected` | device id, platform, state (provisioning/reclaiming), age, threshold | doctor reconcile found a `provisioning`/`reclaiming` device whose time in that state exceeds a driver-derived threshold (`stalledTransition.thresholdMultiplier` over `Driver.estimate`, floored at `stalledTransition.minimumThresholdMs`) — the driver call meant to resolve the transition never did | Doctor | implemented | | `device.crash-detected` | device id, lease id, platform, observed | a leased device was observed `stopped` for `health.stableObservations` consecutive ticks | LeaseHealthMonitor | implemented | | `device.recovered` | device id, lease id, attempts, duration | a crashed leased device was rebooted under its existing lease and passed readiness | LeaseHealthMonitor | implemented | @@ -53,5 +53,5 @@ in short: `subject.past-tense-fact`, emitted post-commit, facts not commands. ## Conventions recap - Every event carries: `timestamp`, `event`, `payload`, emitting module. -- Events are appended to a ring buffer that powers `pitlane events --follow` +- Events are appended to a ring buffer that powers `simlock events --follow` and serves as the audit trail. diff --git a/docs/IDEAS.md b/docs/IDEAS.md index 65e4327..951d141 100644 --- a/docs/IDEAS.md +++ b/docs/IDEAS.md @@ -15,7 +15,7 @@ that frees the constrained capacity. The first warm-pool version is release-driven: it does not proactively boot shutdown devices on daemon startup or merely to fill unused running capacity. -A device enters the warm pool only after an actual lease releases it. Pitlane +A device enters the warm pool only after an actual lease releases it. Simlock also does not provision devices solely to fill the warm pool. Warm devices still shut down after the existing T1 idle timeout; the pool is not refilled afterward until real lease activity releases another device. diff --git a/docs/agent-rules/architecture.md b/docs/agent-rules/architecture.md index 4117262..9b070fd 100644 --- a/docs/agent-rules/architecture.md +++ b/docs/agent-rules/architecture.md @@ -1,6 +1,6 @@ # Agent rules: architecture -Rules for anyone (human or agent) writing pitlane code. Violating these is +Rules for anyone (human or agent) writing simlock code. Violating these is grounds for rejecting a change even if it works. 1. **The core is platform-agnostic.** Core modules must never import platform diff --git a/docs/agent-rules/events.md b/docs/agent-rules/events.md index 4806e4d..b999178 100644 --- a/docs/agent-rules/events.md +++ b/docs/agent-rules/events.md @@ -24,7 +24,7 @@ Rules for defining and emitting events on the daemon's event bus. have to query state that may have moved on. Treat payload shape as a public contract: additive changes only. 7. **Every event carries** `timestamp`, `event`, `payload`, and the emitting - module, and is appended to the ring buffer (this powers `pitlane events` + module, and is appended to the ring buffer (this powers `simlock events` and the audit trail). 8. **New events are documented in the same change.** Adding or modifying an event requires updating [../EVENTS.md](../EVENTS.md) — name, payload, diff --git a/docs/agent-rules/safety.md b/docs/agent-rules/safety.md index 9ab2aa6..61aea38 100644 --- a/docs/agent-rules/safety.md +++ b/docs/agent-rules/safety.md @@ -4,9 +4,9 @@ These invariants protect the user's machine. They are enforced centrally in the cleanup reconciliation loop and the lease path — never bypass them, and never enforce them only inside an individual rule or driver. -1. **Registry-only destruction.** Pitlane only shuts down, erases, or deletes +1. **Registry-only destruction.** Simlock only shuts down, erases, or deletes devices, AVDs, snapshots, and runtimes that exist in its own registry - (i.e. that pitlane created). Everything else on the machine is strictly + (i.e. that simlock created). Everything else on the machine is strictly read-only. This includes `doctor --fix` and `nuke`. 2. **Never touch a leased device.** No cleanup rule, reclaim, or reconcile action may target a device in `leased` state. The reaper filters this diff --git a/docs/known-pitfalls.md b/docs/known-pitfalls.md index ed64ea9..680dd57 100644 --- a/docs/known-pitfalls.md +++ b/docs/known-pitfalls.md @@ -2,7 +2,7 @@ ## Orphaned lease holders (resolved) -The primary lease mechanism is process-held: `pitlane lease` runs in the +The primary lease mechanism is process-held: `simlock lease` runs in the background, holds an open socket to the daemon (connection-alive acts as the heartbeat), and the agent kills the process to release the lease. @@ -38,12 +38,12 @@ backstop; this fix is about a holder outliving its owner, nothing more. ## Crash recovery cannot restore in-device session state `LeaseHealthMonitor` reboots a leased device whose process died outside -pitlane and hands the same lease back to its holder, so the device and its +simlock and hands the same lease back to its holder, so the device and its on-disk state — installed apps, written data — survive the crash intact. **The pitfall:** anything the agent had running *inside* the device died with the process and a reboot cannot bring it back: a launched app, a `log -stream`, an Appium/XCUITest session, a port forward. Pitlane has no visibility +stream`, an Appium/XCUITest session, a port forward. Simlock has no visibility into what was running there, so it cannot even enumerate what was lost, let alone restore it. This is why recovery notifies the holder (`device-unhealthy` / `device-recovered` in held mode) rather than healing @@ -57,7 +57,7 @@ default, so up to ~60s). This debounce is deliberate — `simctl` reports `Booting`/`Shutting Down` and an emulator reads offline in `adb devices` before it answers `getprop`, and either would misfire as a crash without it. -A device erased or deleted outside pitlane is a different, unrecoverable case: +A device erased or deleted outside simlock is a different, unrecoverable case: recovery detects the provenance drift (the same check `doctor` runs) and releases the lease as `device-lost` rather than rebooting it, because the disk state a reboot would resume is no longer provably the agent's. The @@ -66,7 +66,7 @@ get its device rebuilt. **Status:** known and accepted. This is the intended boundary of crash recovery, not a bug — restoring in-device session state would require -pitlane to understand and reproduce whatever the agent was doing inside the +simlock to understand and reproduce whatever the agent was doing inside the device, which is out of scope for a device control plane. **Possible future fix:** none planned. An agent that needs resilience to this @@ -75,7 +75,7 @@ rather than assume continuity. ## Warm-pool purge failures (resolved: quarantine, #21) -Before a released device enters the warm pool, Pitlane attempts to purge the +Before a released device enters the warm pool, Simlock attempts to purge the previous lease's state. A successful purge produces a clean, ready device. **The original pitfall:** the first warm-pool version emitted @@ -95,7 +95,7 @@ the purge on a `Clock`-driven backoff (`warmPool.quarantine.{maxRetries, retryBackoffMs,retryBackoffMultiplier,maxRetryBackoffMs}`); a successful retry returns the device to the warm pool, and exhausting the retry budget destroys it (registry-only, as always). The device stays visible as -`quarantined` in `pitlane status` and `pitlane list --devices` throughout. +`quarantined` in `simlock status` and `simlock list --devices` throughout. `device.purge-failed` still fires as before; `device.quarantined`, `device.quarantine-recovered`, and `device.quarantine-abandoned` are the new follow-up facts (see `docs/EVENTS.md`). diff --git a/docs/loop.md b/docs/loop.md index 9c73298..6826460 100644 --- a/docs/loop.md +++ b/docs/loop.md @@ -1,6 +1,6 @@ # Implementation loop -Historical instructions for implementing a Pitlane stage. Completed stage +Historical instructions for implementing a Simlock stage. Completed stage specifications have been removed from `docs/stages/`. ## Phase 0 — Orient @@ -66,6 +66,6 @@ specifications have been removed from `docs/stages/`. the grep, run the command). - If the stage file conflicts with the code reality you find, STOP and report the conflict instead of improvising. -- If a live test (`PITLANE_LIVE_*`) is required by the stage, run it and +- If a live test (`SIMLOCK_LIVE_*`) is required by the stage, run it and report its actual output; if the environment lacks the prerequisite, say so explicitly — never claim a live test passed that didn't run. diff --git a/e2e/README.md b/e2e/README.md index 8aeaa1a..8e5ea55 100644 --- a/e2e/README.md +++ b/e2e/README.md @@ -26,8 +26,8 @@ the daemon owns real OS resources. ## How isolation works -Each `withDaemon()` allocates a temp `PITLANE_HOME` (so config, state, socket and -log are per-test) and, in the fast lane, points `PITLANE_DRIVERS_MODULE` at +Each `withDaemon()` allocates a temp `SIMLOCK_HOME` (so config, state, socket and +log are per-test) and, in the fast lane, points `SIMLOCK_DRIVERS_MODULE` at `e2e/fake-driver` — the daemon then talks to a scripted driver instead of real hardware without knowing the difference. Both variables are documented in [../docs/CLI.md](../docs/CLI.md#environment-variables). diff --git a/e2e/capacity-cleanup-nuke.test.ts b/e2e/capacity-cleanup-nuke.test.ts index 2e75011..9447e45 100644 --- a/e2e/capacity-cleanup-nuke.test.ts +++ b/e2e/capacity-cleanup-nuke.test.ts @@ -24,7 +24,7 @@ interface StatusResponse { async function status(env: Awaited>): Promise { const result = await env.cli(["status", "--json"]); - if (result.code !== 0) throw new Error(`pitlane status failed: ${result.stderr}`); + if (result.code !== 0) throw new Error(`simlock status failed: ${result.stderr}`); return result.json as StatusResponse; } @@ -32,7 +32,7 @@ async function deviceRows( env: Awaited>, ): Promise<{ id: string; driverDeviceId: string; state: string }[]> { const result = await env.cli(["list", "--devices"]); - if (result.code !== 0) throw new Error(`pitlane list --devices failed: ${result.stderr}`); + if (result.code !== 0) throw new Error(`simlock list --devices failed: ${result.stderr}`); return result.json as { id: string; driverDeviceId: string; state: string }[]; } @@ -67,7 +67,7 @@ async function releaseAndForget( /** * Forces an immediate, synchronous reaper evaluation (rather than waiting on the slow * 60s periodic tick or hoping an unrelated `lease.released` retriggers it) by polling - * `pitlane cleanup` itself until the target reaches `state` -- this is what lets an + * `simlock cleanup` itself until the target reaches `state` -- this is what lets an * idle-threshold test converge in milliseconds without a fixed sleep: each poll tick * both re-checks real elapsed time *and* forces the daemon to act on it. */ diff --git a/e2e/daemon-lifecycle.test.ts b/e2e/daemon-lifecycle.test.ts index c8c2567..39a9737 100644 --- a/e2e/daemon-lifecycle.test.ts +++ b/e2e/daemon-lifecycle.test.ts @@ -49,7 +49,7 @@ describe("daemon lifecycle & recovery", () => { expect(status.json).toMatchObject({ health: "running" }); }); - // Suspected bug: `pitlane daemon status` always writes raw JSON via `writeResult` + // Suspected bug: `simlock daemon status` always writes raw JSON via `writeResult` // (src/cli/index.ts `runDaemon`, the "status" branch) regardless of `--json`, // unlike its siblings `daemon start`/`daemon stop` which both branch on // `values.json` to print "Daemon running"/"Daemon stopping" in human mode. This diff --git a/e2e/doctor-drift.test.ts b/e2e/doctor-drift.test.ts index 35aac8c..54b9298 100644 --- a/e2e/doctor-drift.test.ts +++ b/e2e/doctor-drift.test.ts @@ -20,7 +20,7 @@ interface Finding { async function deviceRows(env: Awaited>): Promise { const result = await env.cli(["list", "--devices"]); - if (result.code !== 0) throw new Error(`pitlane list --devices failed: ${result.stderr}`); + if (result.code !== 0) throw new Error(`simlock list --devices failed: ${result.stderr}`); return result.json as DeviceRow[]; } diff --git a/e2e/fake-driver/fake-driver.ts b/e2e/fake-driver/fake-driver.ts index 0cba986..f8cd489 100644 --- a/e2e/fake-driver/fake-driver.ts +++ b/e2e/fake-driver/fake-driver.ts @@ -44,8 +44,8 @@ const DEFAULT_SCRIPT: FakeDriverPlatformScript = { /** * Driver implementation for the daemon-spawned process the e2e suite drives out of * band. Every behaviour lives in a JSON script file re-read on each operation (env - * var `PITLANE_FAKE_DRIVER_SCRIPT`), and every call is appended as a JSON line to a - * log file (env var `PITLANE_FAKE_DRIVER_LOG`) so a test can assert what the daemon + * var `SIMLOCK_FAKE_DRIVER_SCRIPT`), and every call is appended as a JSON line to a + * log file (env var `SIMLOCK_FAKE_DRIVER_LOG`) so a test can assert what the daemon * did and did not do. A missing script file falls back to permissive defaults -- * never a crash, per the safety rule that a broken test harness should not look like * a broken daemon. diff --git a/e2e/fake-driver/index.ts b/e2e/fake-driver/index.ts index be4c780..9834c2b 100644 --- a/e2e/fake-driver/index.ts +++ b/e2e/fake-driver/index.ts @@ -3,9 +3,9 @@ import { OutOfProcessFakeDriver, type FakeDriverClock } from "./fake-driver.js"; import { DEFAULT_LOG_ENV, DEFAULT_SCRIPT_ENV } from "./types.js"; /** - * The `PITLANE_DRIVERS_MODULE` entry point: substitutes real driver discovery in a - * daemon process spawned by the e2e suite. Reads `PITLANE_FAKE_DRIVER_SCRIPT` and - * `PITLANE_FAKE_DRIVER_LOG` from the environment and hands both fake drivers + * The `SIMLOCK_DRIVERS_MODULE` entry point: substitutes real driver discovery in a + * daemon process spawned by the e2e suite. Reads `SIMLOCK_FAKE_DRIVER_SCRIPT` and + * `SIMLOCK_FAKE_DRIVER_LOG` from the environment and hands both fake drivers * (ios + android) the same paths, so one script file and one call log cover a whole * daemon instance regardless of which platform a test leases against. */ diff --git a/e2e/fake-driver/types.ts b/e2e/fake-driver/types.ts index 0de8ad3..b498c2c 100644 --- a/e2e/fake-driver/types.ts +++ b/e2e/fake-driver/types.ts @@ -82,5 +82,5 @@ export interface FakeDriverLogEntry { readonly arguments: readonly unknown[]; } -export const DEFAULT_SCRIPT_ENV = "PITLANE_FAKE_DRIVER_SCRIPT"; -export const DEFAULT_LOG_ENV = "PITLANE_FAKE_DRIVER_LOG"; +export const DEFAULT_SCRIPT_ENV = "SIMLOCK_FAKE_DRIVER_SCRIPT"; +export const DEFAULT_LOG_ENV = "SIMLOCK_FAKE_DRIVER_LOG"; diff --git a/e2e/heartbeat-ttl.test.ts b/e2e/heartbeat-ttl.test.ts index 9d0d081..573fb42 100644 --- a/e2e/heartbeat-ttl.test.ts +++ b/e2e/heartbeat-ttl.test.ts @@ -12,7 +12,7 @@ interface LeaseRow { async function leaseRows(env: Awaited>): Promise { const result = await env.cli(["list", "--leases"]); - if (result.code !== 0) throw new Error(`pitlane list --leases failed: ${result.stderr}`); + if (result.code !== 0) throw new Error(`simlock list --leases failed: ${result.stderr}`); return result.json as LeaseRow[]; } @@ -58,7 +58,7 @@ describe("sliding TTL and heartbeat", () => { ios: { knownModels: ["iPhone 16"], availableOsVersions: ["18.4"] }, }); - const mcp = await env.mcpClient({ env: { PITLANE_AGENT_ID: "flow6-mcp" } }); + const mcp = await env.mcpClient({ env: { SIMLOCK_AGENT_ID: "flow6-mcp" } }); const cliHeld = env.cliBackground([ "lease", "--platform", diff --git a/e2e/helpers/cli.ts b/e2e/helpers/cli.ts index 5dd405e..2aaba63 100644 --- a/e2e/helpers/cli.ts +++ b/e2e/helpers/cli.ts @@ -27,7 +27,7 @@ export interface CliResult { } /** - * Runs one `pitlane` CLI invocation to completion. Exit-code and structured-error + * Runs one `simlock` CLI invocation to completion. Exit-code and structured-error * assertions read as one-liners: `expect((await cli(env, [...])).code).toBe(13)`. */ export function cli( @@ -46,7 +46,7 @@ export function cli( ? undefined : setTimeout(() => { child.kill("SIGKILL"); - reject(new Error(`pitlane ${args.join(" ")} timed out after ${options.timeout}ms`)); + reject(new Error(`simlock ${args.join(" ")} timed out after ${options.timeout}ms`)); }, options.timeout); child.stdout.on("data", (chunk: Buffer) => { @@ -81,7 +81,7 @@ export interface CliBackgroundHandle { } /** - * Starts a `pitlane` invocation that stays running (held-mode `lease`), for tests + * Starts a `simlock` invocation that stays running (held-mode `lease`), for tests * that need to observe progress, kill the process, or hold a lease across other * assertions. */ diff --git a/e2e/helpers/env.ts b/e2e/helpers/env.ts index 13a3afa..3c3f036 100644 --- a/e2e/helpers/env.ts +++ b/e2e/helpers/env.ts @@ -101,7 +101,7 @@ export interface WithDaemonOptions { readonly configOverrides?: Record; readonly agentId?: string; /** - * "fake" (default) wires `PITLANE_DRIVERS_MODULE` at the scriptable out-of-process + * "fake" (default) wires `SIMLOCK_DRIVERS_MODULE` at the scriptable out-of-process * fake driver. "real" leaves driver discovery alone -- the daemon finds the real * iOS/Android drivers exactly as it would in production -- for the slow, real-SDK * lane; `driverScript`/`driverLog` are inert in that mode (nothing reads them). @@ -114,7 +114,7 @@ export interface TestEnv { readonly socketPath: string; readonly logPath: string; readonly configPath: string; - /** Environment to pass to any spawned pitlane process (CLI, MCP, or the daemon). */ + /** Environment to pass to any spawned simlock process (CLI, MCP, or the daemon). */ readonly env: NodeJS.ProcessEnv; readonly driverScript: DriverScriptControl; readonly driverLog: DriverLogControl; @@ -126,9 +126,9 @@ export interface TestEnv { names: readonly string[], options?: { readonly since?: string; readonly timeout?: number }, ): Promise; - /** Starts the daemon explicitly (`pitlane daemon start`); a no-op if already running. */ + /** Starts the daemon explicitly (`simlock daemon start`); a no-op if already running. */ startDaemon(): Promise; - /** Stops the daemon gracefully (`pitlane daemon stop`), then restarts it explicitly. */ + /** Stops the daemon gracefully (`simlock daemon stop`), then restarts it explicitly. */ restartDaemon(): Promise; /** Sends the given signal directly to the daemon process (default SIGKILL), for * stale-socket-recovery and orphan-sweep tests. Resolves once the process is gone. */ @@ -139,7 +139,7 @@ export interface TestEnv { } /** - * Allocates an isolated `PITLANE_HOME`, wires the fake driver in, and returns a + * Allocates an isolated `SIMLOCK_HOME`, wires the fake driver in, and returns a * `TestEnv`. Registers itself with the module-level `activeEnvs` registry so the one * real `afterEach` above tears it down at the end of the current test: kills any * backgrounded CLI/MCP processes this env spawned, gracefully stops the daemon, then @@ -150,7 +150,7 @@ export interface TestEnv { * it itself (e.g. `waitForLeaseCount(env, 0)`). */ export async function withDaemon(options: WithDaemonOptions = {}): Promise { - const home = await mkdtemp(join(tmpdir(), "pitlane-e2e-")); + const home = await mkdtemp(join(tmpdir(), "simlock-e2e-")); const socketPath = join(home, "daemon.sock"); const logPath = join(home, "daemon.log"); const configPath = join(home, "config.json"); @@ -160,15 +160,15 @@ export async function withDaemon(options: WithDaemonOptions = {}): Promise { const result = await cli(["events", "--since", since], env); if (result.code !== 0) { - throw new Error(`pitlane events failed (exit ${String(result.code)}): ${result.stderr}`); + throw new Error(`simlock events failed (exit ${String(result.code)}): ${result.stderr}`); } return result.stdout .split("\n") diff --git a/e2e/helpers/mcp.ts b/e2e/helpers/mcp.ts index 8275be4..de9172a 100644 --- a/e2e/helpers/mcp.ts +++ b/e2e/helpers/mcp.ts @@ -31,7 +31,7 @@ export interface McpClientHandle { timeout?: number, ): Promise; /** - * The `pitlane mcp` subprocess's stderr captured so far. Piped (not inherited) so a + * The `simlock mcp` subprocess's stderr captured so far. Piped (not inherited) so a * subprocess shutdown quirk (see the flow-2 report note on a stack-overflow logged * during teardown) does not spam the test runner's own output; available here for * a test that needs to inspect it. @@ -54,8 +54,8 @@ export async function mcpClient( command: process.execPath, args: [CLI_ENTRY, "mcp"], // StdioClientTransport only inherits a safe-listed subset of process.env by - // default (DEFAULT_INHERITED_ENV_VARS) -- explicit here so PITLANE_HOME / - // PITLANE_DRIVERS_MODULE / PITLANE_AGENT_ID actually reach the spawned process. + // default (DEFAULT_INHERITED_ENV_VARS) -- explicit here so SIMLOCK_HOME / + // SIMLOCK_DRIVERS_MODULE / SIMLOCK_AGENT_ID actually reach the spawned process. env: toStringEnv({ ...env, ...options.env }), // Piped (not the default "inherit") so a subprocess shutdown quirk doesn't spam // the test runner's own stderr; captured instead, see `stderrOutput()`. @@ -64,7 +64,7 @@ export async function mcpClient( transport.stderr?.on("data", (chunk: Buffer) => { stderrOutput += chunk.toString("utf8"); }); - const client = new Client({ name: "pitlane-e2e", version: "0.0.0" }); + const client = new Client({ name: "simlock-e2e", version: "0.0.0" }); const progress: ProgressNotification["params"][] = []; const logging: LoggingMessageNotification["params"][] = []; diff --git a/e2e/helpers/status.ts b/e2e/helpers/status.ts index b919231..1103df5 100644 --- a/e2e/helpers/status.ts +++ b/e2e/helpers/status.ts @@ -16,20 +16,20 @@ interface LeaseRecord { async function listDevices(env: TestEnv): Promise { const result = await env.cli(["list", "--devices"]); - if (result.code !== 0) throw new Error(`pitlane list --devices failed: ${result.stderr}`); + if (result.code !== 0) throw new Error(`simlock list --devices failed: ${result.stderr}`); return result.json as DeviceRecord[]; } async function listLeases(env: TestEnv): Promise { const result = await env.cli(["list", "--leases"]); - if (result.code !== 0) throw new Error(`pitlane list --leases failed: ${result.stderr}`); + if (result.code !== 0) throw new Error(`simlock list --leases failed: ${result.stderr}`); return result.json as LeaseRecord[]; } /** - * Polls `pitlane list --devices` until the device identified by `driverDeviceId` + * Polls `simlock list --devices` until the device identified by `driverDeviceId` * (the driver-opaque id -- what a lease grant reports as `udid`/`device_id`, *not* - * pitlane's own registry device id) reaches `state`. + * simlock's own registry device id) reaches `state`. */ export async function waitForDeviceState( env: TestEnv, @@ -49,7 +49,7 @@ export async function waitForDeviceState( ); } -/** Polls `pitlane list --leases` until exactly `count` leases are active. */ +/** Polls `simlock list --leases` until exactly `count` leases are active. */ export async function waitForLeaseCount( env: TestEnv, count: number, diff --git a/e2e/lease-lifecycle.test.ts b/e2e/lease-lifecycle.test.ts index bb85777..be99af5 100644 --- a/e2e/lease-lifecycle.test.ts +++ b/e2e/lease-lifecycle.test.ts @@ -69,7 +69,7 @@ describe("lease lifecycle across both frontends", () => { }); const agentId = "flow2-mcp-agent"; - const mcp = await env.mcpClient({ env: { PITLANE_AGENT_ID: agentId } }); + const mcp = await env.mcpClient({ env: { SIMLOCK_AGENT_ID: agentId } }); try { const leaseResult = await mcp.client.callTool({ name: "lease_simulator", diff --git a/e2e/leased-device-crash-recovery.test.ts b/e2e/leased-device-crash-recovery.test.ts index 4682f21..70df1e1 100644 --- a/e2e/leased-device-crash-recovery.test.ts +++ b/e2e/leased-device-crash-recovery.test.ts @@ -227,7 +227,7 @@ describe("leased device crash recovery", () => { label: "held CLI reports the lease ending", timeout: 5_000, }); - // `device_id` here is the registry device id (what `pitlane list --devices` calls + // `device_id` here is the registry device id (what `simlock list --devices` calls // `id`), not the driver `udid` the grant returns on stdout -- these pushes carry // the same identifier the event bus does. const lost = healthLines(held).find((line) => line.event === "lease_lost"); diff --git a/e2e/mcp-session.test.ts b/e2e/mcp-session.test.ts index e900ac0..7755b7c 100644 --- a/e2e/mcp-session.test.ts +++ b/e2e/mcp-session.test.ts @@ -14,7 +14,7 @@ describe("MCP session semantics", () => { ios: { knownModels: ["iPhone 16"], availableOsVersions: ["18.4"] }, android: { knownModels: ["Pixel 8"], availableOsVersions: ["34"] }, }); - const mcp = await env.mcpClient({ env: { PITLANE_AGENT_ID: "flow7-tools" } }); + const mcp = await env.mcpClient({ env: { SIMLOCK_AGENT_ID: "flow7-tools" } }); try { const devicesBefore = await mcp.client.callTool({ name: "list_devices", arguments: {} }); @@ -68,7 +68,7 @@ describe("MCP session semantics", () => { await env.driverScript.set({ ios: { knownModels: ["iPhone 16"], availableOsVersions: ["18.4"] }, }); - const mcp = await env.mcpClient({ env: { PITLANE_AGENT_ID: "flow7-force-release" } }); + const mcp = await env.mcpClient({ env: { SIMLOCK_AGENT_ID: "flow7-force-release" } }); try { const leaseResult = await mcp.client.callTool({ @@ -87,7 +87,7 @@ describe("MCP session semantics", () => { | undefined; return data?.lease_id === leased.lease_id ? notification.params : undefined; }); - expect(warning.logger).toBe("pitlane"); + expect(warning.logger).toBe("simlock"); expect(warning.level).toBe("warning"); // The daemon push carries its own internal registry device id, not the // driver-opaque `device_id` (udid) the MCP lease result reports -- only assert @@ -128,7 +128,7 @@ describe("MCP session semantics", () => { await env.driverScript.set({ ios: { knownModels: ["iPhone 16"], availableOsVersions: ["18.4"] }, }); - const mcp = await env.mcpClient({ env: { PITLANE_AGENT_ID: "flow7-restart" } }); + const mcp = await env.mcpClient({ env: { SIMLOCK_AGENT_ID: "flow7-restart" } }); try { const leaseResult = await mcp.client.callTool({ diff --git a/e2e/slow-android-smoke.test.ts b/e2e/slow-android-smoke.test.ts index 5d866db..88f97e3 100644 --- a/e2e/slow-android-smoke.test.ts +++ b/e2e/slow-android-smoke.test.ts @@ -59,7 +59,7 @@ describe.skipIf(!hasAndroidSdk)( const androidCatalog = platforms.find((platform) => platform.platform === "android"); expect( androidCatalog, - "pitlane catalog reported no android platform -- SDK discovery failed", + "simlock catalog reported no android platform -- SDK discovery failed", ).toBeDefined(); expect(androidCatalog?.models.length ?? 0).toBeGreaterThan(0); const model = androidCatalog?.models[0] as string; @@ -80,9 +80,9 @@ describe.skipIf(!hasAndroidSdk)( expect(lease.code, `lease failed: ${lease.stderr}`).toBe(0); const grant = lease.json as { lease: string; udid: string }; - // The adb serial (e.g. "emulator-5554") is a driver-internal detail pitlane + // The adb serial (e.g. "emulator-5554") is a driver-internal detail simlock // deliberately keeps opaque outside drivers/android (architecture.md #2) -- - // `grant.udid` is pitlane's own AVD name, not the adb serial, so this only + // `grant.udid` is simlock's own AVD name, not the adb serial, so this only // asserts that *an* emulator is actually online, not which one by serial. const onlineSerials = await adbDevices(); expect( @@ -92,8 +92,8 @@ describe.skipIf(!hasAndroidSdk)( const avdNames = await avdManagerList(); expect( - avdNames.some((name) => name.startsWith("pitlane_")), - "expected a pitlane_-prefixed AVD", + avdNames.some((name) => name.startsWith("simlock_")), + "expected a simlock_-prefixed AVD", ).toBe(true); await env.cli(["release", grant.lease]); diff --git a/e2e/slow-ios-smoke.test.ts b/e2e/slow-ios-smoke.test.ts index 063841c..305b4f6 100644 --- a/e2e/slow-ios-smoke.test.ts +++ b/e2e/slow-ios-smoke.test.ts @@ -27,10 +27,10 @@ async function simctlRuntimes(): Promise { return parsed.runtimes.filter((runtime) => runtime.isAvailable).map((runtime) => runtime.version); } -async function deleteStrayPitlaneSimulators(): Promise { +async function deleteStraySimlockSimulators(): Promise { const devices = await simctlDevices(); for (const device of devices) { - if (device.name.startsWith("pitlane-")) { + if (device.name.startsWith("simlock-")) { await execFileAsync("xcrun", ["simctl", "delete", device.udid]).catch(() => undefined); } } @@ -43,7 +43,7 @@ describe.skipIf(process.platform !== "darwin")( { tags: ["slow", "ios"] }, () => { it( - "catalog agrees with simctl, a cold lease boots a real Booted simulator with matching provenance, release keeps it warm, and nuke leaves no pitlane- simulator", + "catalog agrees with simctl, a cold lease boots a real Booted simulator with matching provenance, release keeps it warm, and nuke leaves no simlock- simulator", { timeout: 300_000 }, async () => { const availableRuntimes = await simctlRuntimes(); @@ -61,15 +61,15 @@ describe.skipIf(process.platform !== "darwin")( } ).platforms; const iosCatalog = platforms.find((platform) => platform.platform === "ios"); - expect(iosCatalog, "pitlane catalog reported no iOS platform").toBeDefined(); + expect(iosCatalog, "simlock catalog reported no iOS platform").toBeDefined(); expect( iosCatalog?.models.length ?? 0, - "pitlane catalog reported no iOS models", + "simlock catalog reported no iOS models", ).toBeGreaterThan(0); for (const runtime of iosCatalog?.runtimes ?? []) { expect( availableRuntimes, - "pitlane catalog's runtimes must agree with real simctl", + "simlock catalog's runtimes must agree with real simctl", ).toContain(runtime); } const model = iosCatalog?.models[0] as string; @@ -95,8 +95,8 @@ describe.skipIf(process.platform !== "darwin")( expect(bootedDevice, `simctl does not know about udid ${grant.udid}`).toBeDefined(); expect(bootedDevice?.state).toBe("Booted"); expect( - bootedDevice?.name.startsWith("pitlane-"), - "device name must carry the pitlane- prefix", + bootedDevice?.name.startsWith("simlock-"), + "device name must carry the simlock- prefix", ).toBe(true); // Provenance: both marks exist with the same token. `doctor` is the @@ -117,7 +117,7 @@ describe.skipIf(process.platform !== "darwin")( (finding) => finding.kind === "foreign-provenance-change" && finding.deviceId === registryId, ), - "expected no provenance drift for a device pitlane just created", + "expected no provenance drift for a device simlock just created", ).toBe(false); const release = await env.cli(["release", grant.lease]); @@ -143,11 +143,11 @@ describe.skipIf(process.platform !== "darwin")( await waitFor( async () => - !(await simctlDevices()).some((device) => device.name.startsWith("pitlane-")), - { timeout: 60_000, label: "no pitlane- simulator remains after nuke --delete-devices" }, + !(await simctlDevices()).some((device) => device.name.startsWith("simlock-")), + { timeout: 60_000, label: "no simlock- simulator remains after nuke --delete-devices" }, ); } finally { - await deleteStrayPitlaneSimulators(); + await deleteStraySimlockSimulators(); } }, ); diff --git a/e2e/slow-no-implicit-download.test.ts b/e2e/slow-no-implicit-download.test.ts index eba2b64..6fc9181 100644 --- a/e2e/slow-no-implicit-download.test.ts +++ b/e2e/slow-no-implicit-download.test.ts @@ -77,7 +77,7 @@ describe.skipIf(process.platform !== "darwin")( await env.cli(["nuke", "--delete-devices", "--yes"], { timeout: 60_000 }); } finally { // No cleanup beyond nuke above: this flow must never touch anything besides - // what pitlane itself created and already destroyed. + // what simlock itself created and already destroyed. } const iosRuntimesAfter = await simctlRuntimeNames(); diff --git a/package.json b/package.json index df03a72..f96e95b 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,12 @@ { - "name": "pitlane", + "name": "simlock", "version": "1.0.0", "description": "Control plane for iOS simulators and Android emulators.", "keywords": [], "license": "ISC", "author": "", "bin": { - "pitlane": "dist/cli/main.js" + "simlock": "dist/cli/main.js" }, "type": "module", "scripts": { diff --git a/src/cli/index.test.ts b/src/cli/index.test.ts index 1e903be..3071c3a 100644 --- a/src/cli/index.test.ts +++ b/src/cli/index.test.ts @@ -43,19 +43,19 @@ afterEach(async () => { describe("readLogFile", () => { it("returns just the current log when there is no rotated generation", async () => { const filesystem = new MemoryFilesystem(); - await filesystem.mkdirp("/pitlane"); - await filesystem.writeFileAtomic("/pitlane/daemon.log", "current\n"); + await filesystem.mkdirp("/simlock"); + await filesystem.writeFileAtomic("/simlock/daemon.log", "current\n"); - await expect(readLogFile(filesystem, "/pitlane/daemon.log")).resolves.toBe("current\n"); + await expect(readLogFile(filesystem, "/simlock/daemon.log")).resolves.toBe("current\n"); }); it("prepends the rotated generation so a pre-rotation crash is not lost", async () => { const filesystem = new MemoryFilesystem(); - await filesystem.mkdirp("/pitlane"); - await filesystem.writeFileAtomic("/pitlane/daemon.log.1", "rotated\n"); - await filesystem.writeFileAtomic("/pitlane/daemon.log", "current\n"); + await filesystem.mkdirp("/simlock"); + await filesystem.writeFileAtomic("/simlock/daemon.log.1", "rotated\n"); + await filesystem.writeFileAtomic("/simlock/daemon.log", "current\n"); - await expect(readLogFile(filesystem, "/pitlane/daemon.log")).resolves.toBe( + await expect(readLogFile(filesystem, "/simlock/daemon.log")).resolves.toBe( "rotated\ncurrent\n", ); }); @@ -63,7 +63,7 @@ describe("readLogFile", () => { it("propagates the read failure when neither file exists", async () => { const filesystem = new MemoryFilesystem(); - await expect(readLogFile(filesystem, "/pitlane/daemon.log")).rejects.toThrow(); + await expect(readLogFile(filesystem, "/simlock/daemon.log")).rejects.toThrow(); }); }); @@ -108,7 +108,7 @@ describe("CLI boundary", () => { "lease.request", new DaemonClientError( "REQUESTER_ALREADY_LEASED", - "Requester test-requester already holds lease lse_1; release it (`pitlane release lse_1`) before requesting another device", + "Requester test-requester already holds lease lse_1; release it (`simlock release lse_1`) before requesting another device", ), ); @@ -122,7 +122,7 @@ describe("CLI boundary", () => { const parsed = JSON.parse(output.stderr) as { error: { code: string; message: string } }; expect(parsed.error.code).toBe("REQUESTER_ALREADY_LEASED"); expect(parsed.error.message).toContain("lse_1"); - expect(parsed.error.message).toContain("pitlane release lse_1"); + expect(parsed.error.message).toContain("simlock release lse_1"); }); it("parses human durations and bare milliseconds", () => { @@ -131,8 +131,8 @@ describe("CLI boundary", () => { expect(parseDuration("250")).toBe(250); }); - it("resolves the fallback requester id from PITLANE_AGENT_ID, else the process pid", () => { - expect(fallbackRequesterId({ PITLANE_AGENT_ID: "agent-from-env" })).toBe("agent-from-env"); + it("resolves the fallback requester id from SIMLOCK_AGENT_ID, else the process pid", () => { + expect(fallbackRequesterId({ SIMLOCK_AGENT_ID: "agent-from-env" })).toBe("agent-from-env"); expect(fallbackRequesterId({})).toBe(String(process.pid)); }); @@ -185,7 +185,7 @@ describe("CLI boundary", () => { await expect( runCli(["mcp", help], output.environmentWith({ runMcpStdio: runner })), ).resolves.toBe(0); - expect(output.stdout).toBe("Usage: pitlane mcp\n"); + expect(output.stdout).toBe("Usage: simlock mcp\n"); expect(output.stderr).toBe(""); expect(runner).not.toHaveBeenCalled(); }); @@ -970,7 +970,7 @@ class StubConnection implements DaemonConnection { } async function createHarness() { - const directory = await mkdtemp(join(tmpdir(), "pitlane-cli-")); + const directory = await mkdtemp(join(tmpdir(), "simlock-cli-")); temporaryDirectories.push(directory); const socketPath = join(directory, "daemon.sock"); const clock = new FakeClock(1_000); diff --git a/src/cli/index.ts b/src/cli/index.ts index 9d041d3..4e8b62b 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -7,7 +7,7 @@ import { NodeFilesystem, NodeIpcTransport, NodeParentWatch, - resolvePitlaneHome, + resolveSimlockHome, SystemClock, type Filesystem, type ParentWatch, @@ -28,13 +28,13 @@ import { DaemonClientError, type DaemonConnection } from "../daemon-client/proto export { DaemonClientError, type DaemonConnection } from "../daemon-client/protocol.js"; -const USAGE = `Usage: pitlane [options] +const USAGE = `Usage: simlock [options] Commands: lease, release, status, list, catalog, cleanup, doctor, nuke, events, daemon, config mcp Start the stdio MCP server -Run 'pitlane --help' for command usage.`; +Run 'simlock --help' for command usage.`; /** * Held mode ends with the lease already gone: the daemon released it without @@ -69,7 +69,7 @@ class UsageError extends Error { * banner is no longer dumped to stderr on every failure; it is one flag away. */ function withHelpHint(message: string): string { - return `${message} (run \`pitlane --help\` for usage)`; + return `${message} (run \`simlock --help\` for usage)`; } interface Output { @@ -105,17 +105,17 @@ export interface CliEnvironment { } /** - * Resolves the fallback requester identity from the environment: `PITLANE_AGENT_ID` + * Resolves the fallback requester identity from the environment: `SIMLOCK_AGENT_ID` * when set, else a pid-derived value so callers that never configure a stable id * keep today's behavior. The per-invocation `--agent-id` flag on `lease` (parsed at * that command's own boundary) takes precedence over this default. */ export function fallbackRequesterId(env: NodeJS.ProcessEnv): string { - return env.PITLANE_AGENT_ID ?? String(process.pid); + return env.SIMLOCK_AGENT_ID ?? String(process.pid); } function defaultCliEnvironment(env: NodeJS.ProcessEnv = process.env): CliEnvironment { - const dataDirectory = resolvePitlaneHome(env); + const dataDirectory = resolveSimlockHome(env); const filesystem = new NodeFilesystem(); const clock = new SystemClock(); const ipc = new NodeIpcTransport(); @@ -227,7 +227,7 @@ function cliErrorCode(error: unknown): string { async function runMcp(argv: readonly string[], environment: CliEnvironment): Promise { if (argv.length === 1 && isHelp(argv[0])) { - environment.stdout.write("Usage: pitlane mcp\n"); + environment.stdout.write("Usage: simlock mcp\n"); return 0; } if (argv.length > 0) throw new UsageError("mcp accepts no arguments"); @@ -281,7 +281,7 @@ async function runLease(argv: readonly string[], environment: CliEnvironment): P }); if (values.help) { environment.stdout.write( - "Usage: pitlane lease --platform --device [--os ]\n" + + "Usage: simlock lease --platform --device [--os ]\n" + " [--agent-id ] [--timeout ] [--no-wait] [--detach]\n" + " [--allow-download] [--bind-pid ]\n", ); @@ -391,7 +391,7 @@ async function runRenew(argv: readonly string[], environment: CliEnvironment): P }); const { positionals } = values; if (values.help) { - environment.stdout.write("Usage: pitlane lease renew [--ttl ]\n"); + environment.stdout.write("Usage: simlock lease renew [--ttl ]\n"); return 0; } const leaseId = requiredPositional(positionals, "lease-id"); @@ -413,7 +413,7 @@ async function runRelease(argv: readonly string[], environment: CliEnvironment): }); const { positionals } = values; if (values.help) { - environment.stdout.write("Usage: pitlane release | --all [--yes]\n"); + environment.stdout.write("Usage: simlock release | --all [--yes]\n"); return 0; } if (values.all) { @@ -439,7 +439,7 @@ async function runStatus(argv: readonly string[], environment: CliEnvironment): json: { type: "boolean" }, }); if (values.help) { - environment.stdout.write("Usage: pitlane status [--json]\n"); + environment.stdout.write("Usage: simlock status [--json]\n"); return 0; } const status = await requestOnce(environment, "status.get", {}); @@ -456,7 +456,7 @@ async function runList(argv: readonly string[], environment: CliEnvironment): Pr rules: { type: "boolean" }, }); if (values.help) { - environment.stdout.write("Usage: pitlane list [--devices|--leases|--rules]\n"); + environment.stdout.write("Usage: simlock list [--devices|--leases|--rules]\n"); return 0; } if ([values.devices, values.leases, values.rules].filter(Boolean).length > 1) @@ -473,7 +473,7 @@ async function runCatalog(argv: readonly string[], environment: CliEnvironment): platform: { type: "string" }, }); if (values.help) { - environment.stdout.write("Usage: pitlane catalog [--platform ] [--json]\n"); + environment.stdout.write("Usage: simlock catalog [--platform ] [--json]\n"); return 0; } if (values.platform !== undefined && values.platform !== "ios" && values.platform !== "android") @@ -495,7 +495,7 @@ async function runCleanup(argv: readonly string[], environment: CliEnvironment): rule: { type: "string" }, }); if (values.help) { - environment.stdout.write("Usage: pitlane cleanup [--dry-run] [--rule ]\n"); + environment.stdout.write("Usage: simlock cleanup [--dry-run] [--rule ]\n"); return 0; } writeResult( @@ -514,7 +514,7 @@ async function runDoctor(argv: readonly string[], environment: CliEnvironment): help: { type: "boolean", short: "h" }, }); if (values.help) { - environment.stdout.write("Usage: pitlane doctor [--fix]\n"); + environment.stdout.write("Usage: simlock doctor [--fix]\n"); return 0; } writeResult( @@ -531,11 +531,11 @@ async function runNuke(argv: readonly string[], environment: CliEnvironment): Pr yes: { type: "boolean" }, }); if (values.help) { - environment.stdout.write("Usage: pitlane nuke [--delete-devices] [--yes]\n"); + environment.stdout.write("Usage: simlock nuke [--delete-devices] [--yes]\n"); return 0; } const confirmed = - values.yes ?? (await environment.confirm?.("Nuke Pitlane-managed devices? [y/N] ")); + values.yes ?? (await environment.confirm?.("Nuke Simlock-managed devices? [y/N] ")); if (!confirmed) throw new UsageError("nuke requires confirmation or --yes"); writeResult( environment, @@ -553,7 +553,7 @@ async function runEvents(argv: readonly string[], environment: CliEnvironment): since: { type: "string" }, }); if (values.help) { - environment.stdout.write("Usage: pitlane events [--follow] [--since ]\n"); + environment.stdout.write("Usage: simlock events [--follow] [--since ]\n"); return 0; } const connection = await environment.connect(); @@ -588,7 +588,7 @@ async function runDaemon(argv: readonly string[], environment: CliEnvironment): json: { type: "boolean" }, }); if (command === undefined || isHelp(command) || values.help) { - environment.stdout.write("Usage: pitlane daemon \n"); + environment.stdout.write("Usage: simlock daemon \n"); return 0; } if (values.positionals.length > 0) throw new UsageError("daemon accepts exactly one subcommand"); @@ -656,7 +656,7 @@ async function runConfig(argv: readonly string[], environment: CliEnvironment): const values = commandArgs(argv.slice(1), {}); const [key, rawValue, ...extra] = values.positionals; if (key === undefined || rawValue === undefined || extra.length > 0) - throw new UsageError("Usage: pitlane config set "); + throw new UsageError("Usage: simlock config set "); const config = await environment.readConfigFile(); writeConfigValue(config, key, parseConfigValue(rawValue)); await environment.writeConfigFile(config); @@ -666,7 +666,7 @@ async function runConfig(argv: readonly string[], environment: CliEnvironment): return 0; } if (isHelp(command)) { - environment.stdout.write("Usage: pitlane config [get |set ]\n"); + environment.stdout.write("Usage: simlock config [get |set ]\n"); return 0; } throw new UsageError(`Unknown config command: ${command}`); diff --git a/src/core/cleanup-executor.test.ts b/src/core/cleanup-executor.test.ts index 27ac8a8..508077d 100644 --- a/src/core/cleanup-executor.test.ts +++ b/src/core/cleanup-executor.test.ts @@ -10,7 +10,7 @@ import { ManagedDeviceLifecycle } from "./managed-device-lifecycle.js"; import { Registry } from "./registry.js"; import { SerializedDecision } from "./serialized-decision.js"; -const statePath = "/home/agent/.pitlane/state.json"; +const statePath = "/home/agent/.simlock/state.json"; const spec = { model: "iPhone 16", osVersion: "26.5", platform: "ios" } as const; async function createHarness() { diff --git a/src/core/config.test.ts b/src/core/config.test.ts index a24f1fd..5195600 100644 --- a/src/core/config.test.ts +++ b/src/core/config.test.ts @@ -3,7 +3,7 @@ import { describe, expect, it, vi } from "vitest"; import { MemoryFilesystem, FakeSystemStats } from "../ports/index.js"; import { loadConfig } from "./index.js"; -const configPath = "/home/agent/.pitlane/config.json"; +const configPath = "/home/agent/.simlock/config.json"; const gibibyte = 1024 ** 3; function createStats(): FakeSystemStats { @@ -75,7 +75,7 @@ describe("loadConfig", () => { it("applies a file-level log override", async () => { const filesystem = new MemoryFilesystem(); - await filesystem.mkdirp("/home/agent/.pitlane"); + await filesystem.mkdirp("/home/agent/.simlock"); await filesystem.writeFileAtomic( configPath, JSON.stringify({ log: { level: "debug", rotateBytes: 1024 } }), @@ -87,7 +87,7 @@ describe("loadConfig", () => { it("rejects a log level outside the known set", async () => { const filesystem = new MemoryFilesystem(); - await filesystem.mkdirp("/home/agent/.pitlane"); + await filesystem.mkdirp("/home/agent/.simlock"); await filesystem.writeFileAtomic(configPath, JSON.stringify({ log: { level: "verbose" } })); await expect( @@ -97,7 +97,7 @@ describe("loadConfig", () => { it("rejects a non-positive-integer log rotation cap", async () => { const filesystem = new MemoryFilesystem(); - await filesystem.mkdirp("/home/agent/.pitlane"); + await filesystem.mkdirp("/home/agent/.simlock"); await filesystem.writeFileAtomic(configPath, JSON.stringify({ log: { rotateBytes: 0 } })); await expect( @@ -107,7 +107,7 @@ describe("loadConfig", () => { it("applies a file-level warm-pool quarantine override", async () => { const filesystem = new MemoryFilesystem(); - await filesystem.mkdirp("/home/agent/.pitlane"); + await filesystem.mkdirp("/home/agent/.simlock"); await filesystem.writeFileAtomic( configPath, JSON.stringify({ warmPool: { quarantine: { maxRetries: 1, retryBackoffMs: 1_000 } } }), @@ -124,7 +124,7 @@ describe("loadConfig", () => { it("rejects a non-positive-integer quarantine retry count", async () => { const filesystem = new MemoryFilesystem(); - await filesystem.mkdirp("/home/agent/.pitlane"); + await filesystem.mkdirp("/home/agent/.simlock"); await filesystem.writeFileAtomic( configPath, JSON.stringify({ warmPool: { quarantine: { maxRetries: 0 } } }), @@ -137,7 +137,7 @@ describe("loadConfig", () => { it("rejects a quarantine backoff multiplier below 1", async () => { const filesystem = new MemoryFilesystem(); - await filesystem.mkdirp("/home/agent/.pitlane"); + await filesystem.mkdirp("/home/agent/.simlock"); await filesystem.writeFileAtomic( configPath, JSON.stringify({ warmPool: { quarantine: { retryBackoffMultiplier: 0.5 } } }), @@ -150,7 +150,7 @@ describe("loadConfig", () => { it("applies file values over defaults and explicit overrides over file values", async () => { const filesystem = new MemoryFilesystem(); - await filesystem.mkdirp("/home/agent/.pitlane"); + await filesystem.mkdirp("/home/agent/.simlock"); await filesystem.writeFileAtomic( configPath, JSON.stringify({ @@ -181,7 +181,7 @@ describe("loadConfig", () => { it("deeply merges a partial config file", async () => { const filesystem = new MemoryFilesystem(); - await filesystem.mkdirp("/home/agent/.pitlane"); + await filesystem.mkdirp("/home/agent/.simlock"); await filesystem.writeFileAtomic( configPath, JSON.stringify({ ramBudget: { androidBytesPerDevice: 5 * gibibyte } }), @@ -201,7 +201,7 @@ describe("loadConfig", () => { it("rejects malformed values with the offending key", async () => { const filesystem = new MemoryFilesystem(); - await filesystem.mkdirp("/home/agent/.pitlane"); + await filesystem.mkdirp("/home/agent/.simlock"); await filesystem.writeFileAtomic( configPath, JSON.stringify({ limits: { ios: { maxDevices: "many" } } }), @@ -218,7 +218,7 @@ describe("loadConfig", () => { [{ limits: { android: { maxRunning: "many" } } }, "limits.android.maxRunning"], ])("rejects invalid maxRunning values in every scope", async (contents, path) => { const filesystem = new MemoryFilesystem(); - await filesystem.mkdirp("/home/agent/.pitlane"); + await filesystem.mkdirp("/home/agent/.simlock"); await filesystem.writeFileAtomic(configPath, JSON.stringify(contents)); await expect( @@ -228,7 +228,7 @@ describe("loadConfig", () => { it("accepts a heartbeat interval at the boundary of a quarter of the backstop", async () => { const filesystem = new MemoryFilesystem(); - await filesystem.mkdirp("/home/agent/.pitlane"); + await filesystem.mkdirp("/home/agent/.simlock"); await filesystem.writeFileAtomic( configPath, JSON.stringify({ lease: { heldTtlBackstopMs: 40_000, heartbeatIntervalMs: 10_000 } }), @@ -240,7 +240,7 @@ describe("loadConfig", () => { it("rejects a non-positive-integer heartbeat interval", async () => { const filesystem = new MemoryFilesystem(); - await filesystem.mkdirp("/home/agent/.pitlane"); + await filesystem.mkdirp("/home/agent/.simlock"); await filesystem.writeFileAtomic( configPath, JSON.stringify({ lease: { heartbeatIntervalMs: 0 } }), @@ -253,7 +253,7 @@ describe("loadConfig", () => { it("rejects a heartbeat interval that exceeds a quarter of the backstop", async () => { const filesystem = new MemoryFilesystem(); - await filesystem.mkdirp("/home/agent/.pitlane"); + await filesystem.mkdirp("/home/agent/.simlock"); await filesystem.writeFileAtomic( configPath, JSON.stringify({ lease: { heldTtlBackstopMs: 40_000, heartbeatIntervalMs: 10_001 } }), @@ -267,7 +267,7 @@ describe("loadConfig", () => { it("warns about unknown keys without rejecting the file", async () => { const filesystem = new MemoryFilesystem(); const warn = vi.fn(); - await filesystem.mkdirp("/home/agent/.pitlane"); + await filesystem.mkdirp("/home/agent/.simlock"); await filesystem.writeFileAtomic(configPath, JSON.stringify({ limits: { web: {} } })); await expect( @@ -278,7 +278,7 @@ describe("loadConfig", () => { it("applies a file-level health override", async () => { const filesystem = new MemoryFilesystem(); - await filesystem.mkdirp("/home/agent/.pitlane"); + await filesystem.mkdirp("/home/agent/.simlock"); await filesystem.writeFileAtomic( configPath, JSON.stringify({ @@ -306,7 +306,7 @@ describe("loadConfig", () => { it("rejects a non-boolean health.enabled", async () => { const filesystem = new MemoryFilesystem(); - await filesystem.mkdirp("/home/agent/.pitlane"); + await filesystem.mkdirp("/home/agent/.simlock"); await filesystem.writeFileAtomic(configPath, JSON.stringify({ health: { enabled: "yes" } })); await expect( @@ -323,7 +323,7 @@ describe("loadConfig", () => { [{ health: { maxConcurrentRecoveries: 0 } }, "health.maxConcurrentRecoveries"], ])("rejects invalid health values in every field", async (contents, path) => { const filesystem = new MemoryFilesystem(); - await filesystem.mkdirp("/home/agent/.pitlane"); + await filesystem.mkdirp("/home/agent/.simlock"); await filesystem.writeFileAtomic(configPath, JSON.stringify(contents)); await expect( @@ -334,7 +334,7 @@ describe("loadConfig", () => { it("warns about an unknown key nested under health without rejecting the file", async () => { const filesystem = new MemoryFilesystem(); const warn = vi.fn(); - await filesystem.mkdirp("/home/agent/.pitlane"); + await filesystem.mkdirp("/home/agent/.simlock"); await filesystem.writeFileAtomic(configPath, JSON.stringify({ health: { maxBoltCount: 7 } })); await expect( @@ -345,7 +345,7 @@ describe("loadConfig", () => { it("applies a file-level stalledTransition override", async () => { const filesystem = new MemoryFilesystem(); - await filesystem.mkdirp("/home/agent/.pitlane"); + await filesystem.mkdirp("/home/agent/.simlock"); await filesystem.writeFileAtomic( configPath, JSON.stringify({ @@ -365,7 +365,7 @@ describe("loadConfig", () => { [{ stalledTransition: { minimumThresholdMs: -1 } }, "stalledTransition.minimumThresholdMs"], ])("rejects invalid stalledTransition values in every field", async (contents, path) => { const filesystem = new MemoryFilesystem(); - await filesystem.mkdirp("/home/agent/.pitlane"); + await filesystem.mkdirp("/home/agent/.simlock"); await filesystem.writeFileAtomic(configPath, JSON.stringify(contents)); await expect( diff --git a/src/core/config.ts b/src/core/config.ts index eda76c9..ab2bb35 100644 --- a/src/core/config.ts +++ b/src/core/config.ts @@ -2,7 +2,7 @@ import type { Filesystem, LogLevel, SystemStats } from "../ports/index.js"; const GIBIBYTE = 1024 ** 3; -const DEFAULT_CONFIG_PATH = "~/.pitlane/config.json"; +const DEFAULT_CONFIG_PATH = "~/.simlock/config.json"; export interface Config { readonly limits: { diff --git a/src/core/device-operation-claims.ts b/src/core/device-operation-claims.ts index b552c98..20c4fed 100644 --- a/src/core/device-operation-claims.ts +++ b/src/core/device-operation-claims.ts @@ -4,7 +4,7 @@ * `reclaim` -- the last marks the device as a live, in-process operation so * `StartupConverger#recoverInterruptedReclaims` (which only recovers *unclaimed* * `reclaiming` devices) never mistakes it for one left over from a previous crash, - * and so `pitlane doctor` never reads a long-but-healthy erase as a stalled + * and so `simlock doctor` never reads a long-but-healthy erase as a stalled * transition. Every release takes one, since none of them wait for the purge. * See `LeaseReleaseCoordinator#reclaimInBackground`. */ diff --git a/src/core/device-provisioner.test.ts b/src/core/device-provisioner.test.ts index 01bcf10..7c84df4 100644 --- a/src/core/device-provisioner.test.ts +++ b/src/core/device-provisioner.test.ts @@ -12,7 +12,7 @@ import { ManagedDeviceLifecycle } from "./managed-device-lifecycle.js"; import { Registry } from "./registry.js"; import { SerializedDecision } from "./serialized-decision.js"; -const statePath = "/home/agent/.pitlane/state.json"; +const statePath = "/home/agent/.simlock/state.json"; const spec = { model: "iPhone 16", osVersion: "26.5", platform: "ios" } as const; function reservation(): CapacityReservation & { readonly releaseCount: () => number } { diff --git a/src/core/doctor.test.ts b/src/core/doctor.test.ts index 7919526..38cb681 100644 --- a/src/core/doctor.test.ts +++ b/src/core/doctor.test.ts @@ -44,17 +44,17 @@ describe("Doctor", () => { driver.setManagedReality({ devices: [ { - address: "pitlane-orphan-address", - deviceId: "pitlane-orphan", - driverData: { fakeDeviceId: "pitlane-orphan" }, + address: "simlock-orphan-address", + deviceId: "simlock-orphan", + driverData: { fakeDeviceId: "simlock-orphan" }, runState: "running", }, ], processes: [ { - address: "pitlane-process-address", - deviceId: "pitlane-process", - driverData: { fakeDeviceId: "pitlane-process" }, + address: "simlock-process-address", + deviceId: "simlock-process", + driverData: { fakeDeviceId: "simlock-process" }, }, ], }); @@ -87,8 +87,8 @@ describe("Doctor", () => { devices: [ { createdAt: 1, - driverData: { fakeDeviceId: "pitlane-stale" }, - driverDeviceId: "pitlane-stale", + driverData: { fakeDeviceId: "simlock-stale" }, + driverDeviceId: "simlock-stale", id: "dev_1", spec: { model: "Phone", osVersion: "1", platform: "ios" }, state: "leased", @@ -108,9 +108,9 @@ describe("Doctor", () => { driver.setManagedReality({ devices: [ { - address: "pitlane-stale-address", - deviceId: "pitlane-stale", - driverData: { fakeDeviceId: "pitlane-stale" }, + address: "simlock-stale-address", + deviceId: "simlock-stale", + driverData: { fakeDeviceId: "simlock-stale" }, runState: "stopped", }, ], @@ -143,8 +143,8 @@ describe("Doctor", () => { statePath: "/state.json", }); const device = await registry.registerDevice({ - driverData: { fakeDeviceId: "pitlane-drift" }, - driverDeviceId: "pitlane-drift", + driverData: { fakeDeviceId: "simlock-drift" }, + driverDeviceId: "simlock-drift", provisionDuration: 0, spec: { model: "Phone", osVersion: "1", platform: "ios" }, }); @@ -162,9 +162,9 @@ describe("Doctor", () => { driver.setManagedReality({ devices: [ { - address: "pitlane-drift-address", - deviceId: "pitlane-drift", - driverData: { fakeDeviceId: "pitlane-drift" }, + address: "simlock-drift-address", + deviceId: "simlock-drift", + driverData: { fakeDeviceId: "simlock-drift" }, runState: "stopped", }, ], @@ -208,7 +208,7 @@ describe("Doctor", () => { }); const device = await registry.registerDevice({ driverData: {}, - driverDeviceId: "pitlane-marked", + driverDeviceId: "simlock-marked", provisionDuration: 0, spec: { model: "Phone", osVersion: "1", platform: "ios" }, }); @@ -220,8 +220,8 @@ describe("Doctor", () => { driver.setManagedReality({ devices: [ { - address: "pitlane-marked-address", - deviceId: "pitlane-marked", + address: "simlock-marked-address", + deviceId: "simlock-marked", driverData: {}, mark, runState: "running", @@ -260,7 +260,7 @@ describe("Doctor", () => { } }); - it("ignores provenance marks while Pitlane is itself erasing the device", async () => { + it("ignores provenance marks while Simlock is itself erasing the device", async () => { const clock = new FakeClock(10_000); const eventBus = new EventBus(clock); const registry = await Registry.load({ @@ -272,7 +272,7 @@ describe("Doctor", () => { }); const device = await registry.registerDevice({ driverData: {}, - driverDeviceId: "pitlane-reclaiming", + driverDeviceId: "simlock-reclaiming", provisionDuration: 0, spec: { model: "Phone", osVersion: "1", platform: "ios" }, }); @@ -291,8 +291,8 @@ describe("Doctor", () => { driver.setManagedReality({ devices: [ { - address: "pitlane-reclaiming-address", - deviceId: "pitlane-reclaiming", + address: "simlock-reclaiming-address", + deviceId: "simlock-reclaiming", driverData: {}, mark: { durable: "tok", erasable: undefined, erasableReadable: true }, runState: "running", @@ -338,17 +338,17 @@ describe("Doctor", () => { driver.setManagedReality({ devices: [ { - address: "pitlane-orphan-address", - deviceId: "pitlane-orphan", - driverData: { fakeDeviceId: "pitlane-orphan" }, + address: "simlock-orphan-address", + deviceId: "simlock-orphan", + driverData: { fakeDeviceId: "simlock-orphan" }, runState: "running", }, ], processes: [ { - address: "pitlane-process-address", - deviceId: "pitlane-process", - driverData: { fakeDeviceId: "pitlane-process" }, + address: "simlock-process-address", + deviceId: "simlock-process", + driverData: { fakeDeviceId: "simlock-process" }, }, ], }); @@ -436,7 +436,7 @@ describe("Doctor", () => { expect(eventBus.replay()).toContainEqual(expect.objectContaining({ event: "lease.expired" })); }); - it("reports foreign-state-change for a device booted outside Pitlane, on both platforms", async () => { + it("reports foreign-state-change for a device booted outside Simlock, on both platforms", async () => { const clock = new FakeClock(10_000); const eventBus = new EventBus(clock); const registry = await Registry.load({ @@ -446,15 +446,15 @@ describe("Doctor", () => { idGenerator: sequence(), statePath: "/state.json", }); - const iosDevice = await shutdownDevice(registry, "pitlane-ios-1", "ios"); - const androidDevice = await shutdownDevice(registry, "pitlane_android-1", "android"); + const iosDevice = await shutdownDevice(registry, "simlock-ios-1", "ios"); + const androidDevice = await shutdownDevice(registry, "simlock_android-1", "android"); const iosDriver = new FakeDriver({ clock, platform: "ios" }); iosDriver.setManagedReality({ devices: [ { - address: "pitlane-ios-1-address", - deviceId: "pitlane-ios-1", + address: "simlock-ios-1-address", + deviceId: "simlock-ios-1", driverData: {}, runState: "running", }, @@ -465,8 +465,8 @@ describe("Doctor", () => { androidDriver.setManagedReality({ devices: [ { - address: "pitlane_android-1-address", - deviceId: "pitlane_android-1", + address: "simlock_android-1-address", + deviceId: "simlock_android-1", driverData: {}, runState: "running", }, @@ -500,7 +500,7 @@ describe("Doctor", () => { ]); }); - it("reports foreign-state-change for a device shut down outside Pitlane, on both platforms", async () => { + it("reports foreign-state-change for a device shut down outside Simlock, on both platforms", async () => { const clock = new FakeClock(10_000); const eventBus = new EventBus(clock); const registry = await Registry.load({ @@ -510,15 +510,15 @@ describe("Doctor", () => { idGenerator: sequence(), statePath: "/state.json", }); - const iosDevice = await readyDevice(registry, "pitlane-ios-2", "ios"); - const androidDevice = await readyDevice(registry, "pitlane_android-2", "android"); + const iosDevice = await readyDevice(registry, "simlock-ios-2", "ios"); + const androidDevice = await readyDevice(registry, "simlock_android-2", "android"); const iosDriver = new FakeDriver({ clock, platform: "ios" }); iosDriver.setManagedReality({ devices: [ { - address: "pitlane-ios-2-address", - deviceId: "pitlane-ios-2", + address: "simlock-ios-2-address", + deviceId: "simlock-ios-2", driverData: {}, runState: "stopped", }, @@ -529,8 +529,8 @@ describe("Doctor", () => { androidDriver.setManagedReality({ devices: [ { - address: "pitlane_android-2-address", - deviceId: "pitlane_android-2", + address: "simlock_android-2-address", + deviceId: "simlock_android-2", driverData: {}, runState: "stopped", }, @@ -574,14 +574,14 @@ describe("Doctor", () => { idGenerator: sequence(), statePath: "/state.json", }); - await readyDevice(registry, "pitlane-transition", "ios"); + await readyDevice(registry, "simlock-transition", "ios"); const driver = new FakeDriver({ clock, platform: "ios" }); driver.setManagedReality({ devices: [ { - address: "pitlane-transition-address", - deviceId: "pitlane-transition", + address: "simlock-transition-address", + deviceId: "simlock-transition", driverData: {}, runState: "transitioning", }, @@ -614,11 +614,11 @@ describe("Doctor", () => { }); await registry.registerDevice({ driverData: {}, - driverDeviceId: "pitlane-provisioning", + driverDeviceId: "simlock-provisioning", provisionDuration: 0, spec: { model: "Phone", osVersion: "1", platform: "ios" }, }); - const reclaiming = await readyDevice(registry, "pitlane-reclaiming", "ios"); + const reclaiming = await readyDevice(registry, "simlock-reclaiming", "ios"); const lease = await registry.createLease({ deviceId: reclaiming.id, mode: "held", @@ -631,14 +631,14 @@ describe("Doctor", () => { driver.setManagedReality({ devices: [ { - address: "pitlane-provisioning-address", - deviceId: "pitlane-provisioning", + address: "simlock-provisioning-address", + deviceId: "simlock-provisioning", driverData: {}, runState: "stopped", }, { - address: "pitlane-reclaiming-address", - deviceId: "pitlane-reclaiming", + address: "simlock-reclaiming-address", + deviceId: "simlock-reclaiming", driverData: {}, runState: "stopped", }, @@ -677,7 +677,7 @@ describe("Doctor", () => { }); const device = await registry.registerDevice({ driverData: {}, - driverDeviceId: "pitlane-stuck", + driverDeviceId: "simlock-stuck", provisionDuration: 0, spec: { model: "Phone", osVersion: "1", platform: "ios" }, }); @@ -725,7 +725,7 @@ describe("Doctor", () => { }); const device = await registry.registerDevice({ driverData: {}, - driverDeviceId: "pitlane-claimed", + driverDeviceId: "simlock-claimed", provisionDuration: 0, spec: { model: "Phone", osVersion: "1", platform: "ios" }, }); @@ -783,7 +783,7 @@ describe("Doctor", () => { }); await registry.registerDevice({ driverData: {}, - driverDeviceId: "pitlane-slow", + driverDeviceId: "simlock-slow", provisionDuration: 0, spec: { model: "Phone", osVersion: "1", platform: "ios" }, }); @@ -813,7 +813,7 @@ describe("Doctor", () => { statePath: "/state.json", }); const driver = new FakeDriver({ clock, estimateMs: { reclaim: 2_000 }, platform: "ios" }); - const device = await readyDevice(registry, "pitlane-killed", "ios"); + const device = await readyDevice(registry, "simlock-killed", "ios"); const lease = await registry.createLease({ deviceId: device.id, mode: "held", @@ -863,7 +863,7 @@ describe("Doctor", () => { }); const device = await registry.registerDevice({ driverData: {}, - driverDeviceId: "pitlane-stuck", + driverDeviceId: "simlock-stuck", provisionDuration: 0, spec: { model: "Phone", osVersion: "1", platform: "ios" }, }); @@ -902,7 +902,7 @@ describe("Doctor", () => { }); const device = await registry.registerDevice({ driverData: {}, - driverDeviceId: "pitlane-stuck", + driverDeviceId: "simlock-stuck", provisionDuration: 0, spec: { model: "Phone", osVersion: "1", platform: "ios" }, }); @@ -911,8 +911,8 @@ describe("Doctor", () => { driver.setManagedReality({ devices: [ { - address: "pitlane-stuck-address", - deviceId: "pitlane-stuck", + address: "simlock-stuck-address", + deviceId: "simlock-stuck", driverData: {}, runState: "stopped", }, @@ -962,7 +962,7 @@ describe("Doctor", () => { { createdAt: 1, driverData: {}, - driverDeviceId: "pitlane-stuck", + driverDeviceId: "simlock-stuck", id: "dev_1", lastLeaseEndedAt: 1, spec: { model: "Phone", osVersion: "1", platform: "ios" }, @@ -1018,21 +1018,21 @@ describe("Doctor", () => { idGenerator: sequence(), statePath: "/state.json", }); - const bootedOutside = await shutdownDevice(registry, "pitlane-booted", "ios"); - const shutdownOutside = await readyDevice(registry, "pitlane-shutdown", "ios"); + const bootedOutside = await shutdownDevice(registry, "simlock-booted", "ios"); + const shutdownOutside = await readyDevice(registry, "simlock-shutdown", "ios"); const driver = new FakeDriver({ clock, platform: "ios" }); driver.setManagedReality({ devices: [ { - address: "pitlane-booted-address", - deviceId: "pitlane-booted", + address: "simlock-booted-address", + deviceId: "simlock-booted", driverData: {}, runState: "running", }, { - address: "pitlane-shutdown-address", - deviceId: "pitlane-shutdown", + address: "simlock-shutdown-address", + deviceId: "simlock-shutdown", driverData: {}, runState: "stopped", }, @@ -1063,7 +1063,7 @@ describe("Doctor", () => { idGenerator: sequence(), statePath: "/state.json", }); - const device = await readyDevice(registry, "pitlane-leased", "ios"); + const device = await readyDevice(registry, "simlock-leased", "ios"); await registry.createLease({ deviceId: device.id, mode: "held", @@ -1075,8 +1075,8 @@ describe("Doctor", () => { driver.setManagedReality({ devices: [ { - address: "pitlane-leased-address", - deviceId: "pitlane-leased", + address: "simlock-leased-address", + deviceId: "simlock-leased", driverData: {}, runState: "stopped", }, @@ -1119,21 +1119,21 @@ describe("Doctor", () => { idGenerator: sequence(), statePath: "/state.json", }); - const bootedOutside = await shutdownDevice(registry, "pitlane-booted", "ios"); - const shutdownOutside = await readyDevice(registry, "pitlane-shutdown", "ios"); + const bootedOutside = await shutdownDevice(registry, "simlock-booted", "ios"); + const shutdownOutside = await readyDevice(registry, "simlock-shutdown", "ios"); const driver = new FakeDriver({ clock, platform: "ios" }); driver.setManagedReality({ devices: [ { - address: "pitlane-booted-address", - deviceId: "pitlane-booted", + address: "simlock-booted-address", + deviceId: "simlock-booted", driverData: {}, runState: "running", }, { - address: "pitlane-shutdown-address", - deviceId: "pitlane-shutdown", + address: "simlock-shutdown-address", + deviceId: "simlock-shutdown", driverData: {}, runState: "stopped", }, diff --git a/src/core/doctor.ts b/src/core/doctor.ts index be1e598..cf68b15 100644 --- a/src/core/doctor.ts +++ b/src/core/doctor.ts @@ -46,12 +46,12 @@ export type DoctorFinding = /** * `erased` -- the durable mark stands but the erasable one is gone: the device - * was erased or wiped outside Pitlane. + * was erased or wiped outside Simlock. * `mark-mismatch` -- both regions carry a token but they disagree, so * something re-marked one region independently. * `durable-mark-missing` -- the durable region carries no token at all: the * device definition was recreated, or foreign tooling rewrote it. On Android - * that also catches an `avdmanager create` reusing a `pitlane_` name, which + * that also catches an `avdmanager create` reusing a `simlock_` name, which * the prefix match in `listManaged` would otherwise adopt silently. */ export type ProvenanceDrift = "erased" | "mark-mismatch" | "durable-mark-missing"; @@ -295,7 +295,7 @@ function registryDriftFindings( } // `expected === undefined` means the registry is mid-transition (provisioning, - // reclaiming, deleted). Pitlane is acting on the device itself in those states, + // reclaiming, deleted). Simlock is acting on the device itself in those states, // including erasing it, so neither run state nor marks are compared. const expected = expectedRunState(device.state); const observed = observedDevices.get(deviceKey); @@ -327,7 +327,7 @@ function registryDriftFindings( } /** - * A `provisioning` / `reclaiming` device is normally in-flight work Pitlane itself is + * A `provisioning` / `reclaiming` device is normally in-flight work Simlock itself is * driving (see `expectedRunState`), not drift -- but only up to a point. Past a * driver-derived threshold it stops being "still working" and becomes a stall: the * driver call that was supposed to resolve it never did. This is a documented failure diff --git a/src/core/driver.ts b/src/core/driver.ts index adf5891..b4ecb3b 100644 --- a/src/core/driver.ts +++ b/src/core/driver.ts @@ -12,8 +12,8 @@ export interface DriverDevice { /** * The opaque string platform tooling accepts right now -- a simctl UDID for iOS, an adb * serial (`emulator-`) for Android. The core carries it without interpreting it; only - * the owning driver module knows what it means. Unlike `deviceId` (Pitlane's own, stable - * `pitlane-`/`pitlane_`-prefixed name proving Pitlane created the device), this can change + * the owning driver module knows what it means. Unlike `deviceId` (Simlock's own, stable + * `simlock-`/`simlock_`-prefixed name proving Simlock created the device), this can change * across a boot -- see `Driver.makeReady`. */ readonly address: string; @@ -28,7 +28,7 @@ export interface DriverDevice { export type ObservedRunState = "running" | "stopped" | "transitioning"; /** - * Provenance-mark readings for one managed device. Pitlane writes the same + * Provenance-mark readings for one managed device. Simlock writes the same * token into two regions of a device it owns: one that survives a fresh-state * erase and one the erase destroys. Comparing the pair is what makes a foreign * erase visible -- an erased device still exists and still boots, so run-state @@ -58,9 +58,9 @@ export interface ObservedDevice extends DriverDevice { /** Reality observable by a driver without trusting the registry. */ export interface DriverReality { - /** Devices whose platform-owned name proves that Pitlane created them. */ + /** Devices whose platform-owned name proves that Simlock created them. */ readonly devices: readonly ObservedDevice[]; - /** Running, Pitlane-attributable device processes not necessarily in the registry. */ + /** Running, Simlock-attributable device processes not necessarily in the registry. */ readonly processes: readonly DriverDevice[]; } diff --git a/src/core/fake-driver.ts b/src/core/fake-driver.ts index 6aad283..6c0eb47 100644 --- a/src/core/fake-driver.ts +++ b/src/core/fake-driver.ts @@ -219,8 +219,8 @@ export class FakeDriver implements Driver { setManagedReality(reality: DriverReality): void { const managed = { - devices: reality.devices.filter(isPitlaneManaged), - processes: reality.processes.filter(isPitlaneManaged), + devices: reality.devices.filter(isSimlockManaged), + processes: reality.processes.filter(isSimlockManaged), }; this.#managedReality = cloneReality(managed); for (const device of reality.devices) { @@ -272,8 +272,8 @@ function addressFor(deviceId: string, bootCount: number): string { return `${deviceId}-addr-${bootCount}`; } -function isPitlaneManaged(device: DriverDevice): boolean { - return device.deviceId.startsWith("pitlane-") || device.deviceId.startsWith("pitlane_"); +function isSimlockManaged(device: DriverDevice): boolean { + return device.deviceId.startsWith("simlock-") || device.deviceId.startsWith("simlock_"); } function runStateFor(status: "provisioned" | "ready" | "shutdown"): ObservedRunState { diff --git a/src/core/lease-acquisition-coordinator.test.ts b/src/core/lease-acquisition-coordinator.test.ts index 05cbb46..552b87f 100644 --- a/src/core/lease-acquisition-coordinator.test.ts +++ b/src/core/lease-acquisition-coordinator.test.ts @@ -20,7 +20,7 @@ import { SerializedDecision } from "./serialized-decision.js"; import { QueueTimeoutError, WaitQueue } from "./wait-queue.js"; const gibibyte = 1024 ** 3; -const statePath = "/home/agent/.pitlane/state.json"; +const statePath = "/home/agent/.simlock/state.json"; const request = { model: "iPhone 16", osVersion: "26.5", platform: "ios" } as const; function config(maxDevices = 1): Config { diff --git a/src/core/lease-engine.test.ts b/src/core/lease-engine.test.ts index fbb1604..161fc1b 100644 --- a/src/core/lease-engine.test.ts +++ b/src/core/lease-engine.test.ts @@ -14,7 +14,7 @@ import { } from "./index.js"; const gibibyte = 1024 ** 3; -const statePath = "/home/agent/.pitlane/state.json"; +const statePath = "/home/agent/.simlock/state.json"; const request = { model: "iPhone 16", osVersion: "26.5", platform: "ios" } as const; function config(overrides: Partial = {}): Config { @@ -1019,7 +1019,7 @@ describe("LeaseEngine startup reclaim backgrounding (#43)", () => { it("recovers a background reclaim interrupted by a daemon crash on the next start", async () => { const filesystem = new MemoryFilesystem(); - const restartStatePath = "/home/agent/.pitlane/restart-state.json"; + const restartStatePath = "/home/agent/.simlock/restart-state.json"; let nextId = 1; const idGenerator = { generate: () => `${nextId++}` }; const systemStats = () => diff --git a/src/core/lease-health-monitor.test.ts b/src/core/lease-health-monitor.test.ts index 47ccfce..c19e5f6 100644 --- a/src/core/lease-health-monitor.test.ts +++ b/src/core/lease-health-monitor.test.ts @@ -12,7 +12,7 @@ import { ManagedDeviceLifecycle } from "./managed-device-lifecycle.js"; import { SerializedDecision } from "./serialized-decision.js"; const gibibyte = 1024 ** 3; -const statePath = "/home/agent/.pitlane/state.json"; +const statePath = "/home/agent/.simlock/state.json"; const spec = { model: "iPhone 16", osVersion: "26.5", platform: "ios" } as const; function config(overrides: Partial = {}): Config { @@ -107,7 +107,7 @@ async function seedLeased( options: { readonly driverDeviceId?: string; readonly requesterId?: string } = {}, ) { deviceCounter += 1; - const driverDeviceId = options.driverDeviceId ?? `pitlane-${deviceCounter}`; + const driverDeviceId = options.driverDeviceId ?? `simlock-${deviceCounter}`; const device = await harness.registry.registerDevice({ driverData: { fakeDeviceId: driverDeviceId }, driverDeviceId, @@ -428,18 +428,18 @@ describe("LeaseHealthMonitor", () => { it("gives up for provenance drift only while running; the same drift while stopped follows the crash path instead", async () => { const harness = await createHarness(); const { device: runningDevice, lease: runningLease } = await seedLeased(harness, { - driverDeviceId: "pitlane-running", + driverDeviceId: "simlock-running", }); const { device: stoppedDevice } = await seedLeased(harness, { - driverDeviceId: "pitlane-stopped", + driverDeviceId: "simlock-stopped", requesterId: "agent-2", }); const drift: ObservedMark = { durable: "tok-a", erasable: undefined, erasableReadable: true }; harness.driver.setManagedReality({ devices: [ - observedDevice("pitlane-running", "running", drift), - observedDevice("pitlane-stopped", "stopped", drift), + observedDevice("simlock-running", "running", drift), + observedDevice("simlock-stopped", "stopped", drift), ], processes: [], }); @@ -496,11 +496,11 @@ describe("LeaseHealthMonitor", () => { it("caps concurrent recoveries at maxConcurrentRecoveries, leaving a second crashed device for a later tick", async () => { const harness = await createHarness({ maxConcurrentRecoveries: 1 }); - await seedLeased(harness, { driverDeviceId: "pitlane-a" }); - await seedLeased(harness, { driverDeviceId: "pitlane-b", requesterId: "agent-2" }); + await seedLeased(harness, { driverDeviceId: "simlock-a" }); + await seedLeased(harness, { driverDeviceId: "simlock-b", requesterId: "agent-2" }); harness.driver.setManagedReality({ - devices: [observedDevice("pitlane-a", "stopped"), observedDevice("pitlane-b", "stopped")], + devices: [observedDevice("simlock-a", "stopped"), observedDevice("simlock-b", "stopped")], processes: [], }); harness.driver.hangMakeReady(); @@ -520,16 +520,16 @@ describe("LeaseHealthMonitor", () => { it("keeps ticking on schedule for other devices while a slow recovery stays in flight, without double-dispatching it", async () => { const harness = await createHarness(); - await seedLeased(harness, { driverDeviceId: "pitlane-hung" }); + await seedLeased(harness, { driverDeviceId: "simlock-hung" }); const { device: healthyDevice } = await seedLeased(harness, { - driverDeviceId: "pitlane-healthy", + driverDeviceId: "simlock-healthy", requesterId: "agent-2", }); harness.driver.setManagedReality({ devices: [ - observedDevice("pitlane-hung", "stopped"), - observedDevice("pitlane-healthy", "running"), + observedDevice("simlock-hung", "stopped"), + observedDevice("simlock-healthy", "running"), ], processes: [], }); diff --git a/src/core/lease-health-monitor.ts b/src/core/lease-health-monitor.ts index a32dcd1..623935e 100644 --- a/src/core/lease-health-monitor.ts +++ b/src/core/lease-health-monitor.ts @@ -43,7 +43,7 @@ interface CrashCounters { /** * Periodically observes `leased` devices, reboots one whose process crashed - * outside pitlane under its existing lease, and releases the lease as + * outside simlock under its existing lease, and releases the lease as * `device-lost` when recovery is impossible or exhausted. Not event-triggered * -- purely a `Clock`-driven tick, modelled on `CleanupReaper`. * diff --git a/src/core/lease-lifecycle.test.ts b/src/core/lease-lifecycle.test.ts index 014d8e7..0823979 100644 --- a/src/core/lease-lifecycle.test.ts +++ b/src/core/lease-lifecycle.test.ts @@ -6,7 +6,7 @@ import { LeaseExpiryScheduler } from "./lease-expiry-scheduler.js"; import { DetachedLeaseHeartbeatError, LeaseLifecycle } from "./lease-lifecycle.js"; import { Registry } from "./registry.js"; -const statePath = "/home/agent/.pitlane/state.json"; +const statePath = "/home/agent/.simlock/state.json"; async function createHarness(options: { readonly filesystem?: Filesystem } = {}) { const clock = new FakeClock(1_000); diff --git a/src/core/lease-ports.ts b/src/core/lease-ports.ts index 22eafc6..390afcf 100644 --- a/src/core/lease-ports.ts +++ b/src/core/lease-ports.ts @@ -33,7 +33,7 @@ export interface LeaseExpirer { expire(leaseId: string): Promise; } -/** Read-only device catalog used by the `pitlane catalog` command and MCP tool. */ +/** Read-only device catalog used by the `simlock catalog` command and MCP tool. */ export interface CatalogReader { listCatalog(platform?: Platform): Promise; } diff --git a/src/core/lease-release-coordinator.test.ts b/src/core/lease-release-coordinator.test.ts index 33709be..820367f 100644 --- a/src/core/lease-release-coordinator.test.ts +++ b/src/core/lease-release-coordinator.test.ts @@ -10,7 +10,7 @@ import { LeaseReleaseCoordinator } from "./lease-release-coordinator.js"; import { Registry, UnknownLeaseError, type ReleasedLease } from "./registry.js"; import { SerializedDecision } from "./serialized-decision.js"; -const statePath = "/home/agent/.pitlane/state.json"; +const statePath = "/home/agent/.simlock/state.json"; async function flush(): Promise { for (let count = 0; count < 10; count += 1) { @@ -439,7 +439,7 @@ describe("LeaseReleaseCoordinator", () => { // Claimed while the reclaim is in flight -- this is what keeps // StartupConverger#recoverInterruptedReclaims from treating a reclaim this // process just started as one orphaned by a *previous* crash, and what keeps - // `pitlane doctor` from reading a long-but-healthy erase as a stalled transition. + // `simlock doctor` from reading a long-but-healthy erase as a stalled transition. expect(harness.claims.isClaimed(granted.device.id)).toBe(true); expect(harness.claims.operationFor(granted.device.id)).toBe("reclaim"); diff --git a/src/core/lease-release-coordinator.ts b/src/core/lease-release-coordinator.ts index 3f198a5..df710c9 100644 --- a/src/core/lease-release-coordinator.ts +++ b/src/core/lease-release-coordinator.ts @@ -121,7 +121,7 @@ export class LeaseReleaseCoordinator /** * Awaits every reclaim currently running in the background. Nothing on a - * client's path calls this -- a graceful daemon shutdown does, so a `pitlane + * client's path calls this -- a graceful daemon shutdown does, so a `simlock * daemon stop` still leaves the pool in the same settled shape an inline * reclaim used to. An ungraceful death instead leaves those devices * `reclaiming` for `StartupConverger#recoverInterruptedReclaims`. @@ -221,7 +221,7 @@ export class LeaseReleaseCoordinator * Claims the device for the reclaim's duration. Two readers depend on that claim * to tell a live in-process reclaim apart from an abandoned one: `StartupConverger * #recoverInterruptedReclaims`, which must not mistake a reclaim this process just - * started for one orphaned by a *previous* crash, and `pitlane doctor`'s + * started for one orphaned by a *previous* crash, and `simlock doctor`'s * stalled-transition finding, which must not read a legitimately long erase as a * driver call that never returned. Both exclude claimed devices for exactly this * reason; a reclaim orphaned by a crash carries no claim in the new process, so @@ -231,7 +231,7 @@ export class LeaseReleaseCoordinator * reclaim settles -- no caller is awaiting this promise, so a rejection here would * otherwise be unhandled. A purge that fails at the driver is not that case: it is * handled inside `WarmPoolCoordinator#reclaim`, which quarantines the device - * instead of rejecting, and stays visible in `pitlane status` and + * instead of rejecting, and stays visible in `simlock status` and * `device.purge-failed`. */ #reclaimInBackground(released: ReleasedLease): void { diff --git a/src/core/managed-device-lifecycle.test.ts b/src/core/managed-device-lifecycle.test.ts index ff290e2..1f5a9fb 100644 --- a/src/core/managed-device-lifecycle.test.ts +++ b/src/core/managed-device-lifecycle.test.ts @@ -10,7 +10,7 @@ import { ManagedDeviceLifecycle } from "./managed-device-lifecycle.js"; import { Registry } from "./registry.js"; import { SerializedDecision } from "./serialized-decision.js"; -const statePath = "/home/agent/.pitlane/state.json"; +const statePath = "/home/agent/.simlock/state.json"; async function createHarness() { const clock = new FakeClock(1_000); diff --git a/src/core/managed-device-lifecycle.ts b/src/core/managed-device-lifecycle.ts index 819b9c7..9b1d86d 100644 --- a/src/core/managed-device-lifecycle.ts +++ b/src/core/managed-device-lifecycle.ts @@ -135,7 +135,7 @@ export class ManagedDeviceLifecycle { /** * Reboots a device that is currently leased and whose underlying process - * died outside pitlane, so the lease can continue on the same device. The + * died outside simlock, so the lease can continue on the same device. The * device stays `leased` throughout: this performs no registry transition * and emits no event, deliberately -- the caller (a `LeaseHealthMonitor`) * owns deciding what happened and telling the holder. diff --git a/src/core/nuke-service.ts b/src/core/nuke-service.ts index 1be9628..88e4332 100644 --- a/src/core/nuke-service.ts +++ b/src/core/nuke-service.ts @@ -14,7 +14,7 @@ export interface AcquisitionMaintenance { endMaintenance(): Promise; } -/** Read-only registry view used to select Pitlane-owned device records. */ +/** Read-only registry view used to select Simlock-owned device records. */ export interface NukeRegistryView { readonly snapshot: { readonly devices: readonly DeviceRecord[]; diff --git a/src/core/quarantine-coordinator.ts b/src/core/quarantine-coordinator.ts index 31a6432..2e3aa30 100644 --- a/src/core/quarantine-coordinator.ts +++ b/src/core/quarantine-coordinator.ts @@ -209,7 +209,7 @@ export class QuarantineCoordinator { // against a grant, still counted against capacity -- rather than either // reusing a dirty device or losing track of one that never got cleaned // up. No further retry is scheduled, so this is a terminal state that - // only an operator (`pitlane doctor` / `nuke`) leaves; emit it as its own + // only an operator (`simlock doctor` / `nuke`) leaves; emit it as its own // fact rather than letting a device strand itself in silence. await this.options.decisions.run(() => this.options.registry.strandQuarantine(device.id, attempts), diff --git a/src/core/reaper.test.ts b/src/core/reaper.test.ts index 303f975..ace95db 100644 --- a/src/core/reaper.test.ts +++ b/src/core/reaper.test.ts @@ -18,7 +18,7 @@ import { ManagedDeviceLifecycle } from "./managed-device-lifecycle.js"; import { SerializedDecision } from "./serialized-decision.js"; const gibibyte = 1024 ** 3; -const statePath = "/home/agent/.pitlane/state.json"; +const statePath = "/home/agent/.simlock/state.json"; const spec = { model: "iPhone 16", osVersion: "26.5", platform: "ios" } as const; /** diff --git a/src/core/reaper.ts b/src/core/reaper.ts index ac96f25..bdd281c 100644 --- a/src/core/reaper.ts +++ b/src/core/reaper.ts @@ -49,7 +49,7 @@ export class CleanupReaper { this.#armTick(); } - /** Rules currently registered, for `pitlane list --rules` and `--rule` selection. */ + /** Rules currently registered, for `simlock list --rules` and `--rule` selection. */ get rules(): readonly CleanupRule[] { return this.#automaticRules; } diff --git a/src/core/registry.test.ts b/src/core/registry.test.ts index 72c6260..f9e621e 100644 --- a/src/core/registry.test.ts +++ b/src/core/registry.test.ts @@ -4,7 +4,7 @@ import { EventBus } from "../bus/index.js"; import { FakeClock, MemoryFilesystem } from "../ports/index.js"; import { type DeviceSpec, Registry, RegistryEventError, UnknownDeviceError } from "./index.js"; -const statePath = "/home/agent/.pitlane/state.json"; +const statePath = "/home/agent/.simlock/state.json"; const spec: DeviceSpec = { model: "iPhone 16", osVersion: "26.5", platform: "ios" }; class ObservingFilesystem extends MemoryFilesystem { @@ -90,7 +90,7 @@ describe("Registry", () => { it("loads a legacy warm record as busy reclaiming rather than eligible ready inventory", async () => { const clock = new FakeClock(1_000); const filesystem = new MemoryFilesystem(); - await filesystem.mkdirp("/home/agent/.pitlane"); + await filesystem.mkdirp("/home/agent/.simlock"); await filesystem.writeFileAtomic( statePath, JSON.stringify({ @@ -123,7 +123,7 @@ describe("Registry", () => { it("preserves unknown persisted fields when saving a later mutation", async () => { const clock = new FakeClock(1_000); const filesystem = new MemoryFilesystem(); - await filesystem.mkdirp("/home/agent/.pitlane"); + await filesystem.mkdirp("/home/agent/.simlock"); await filesystem.writeFileAtomic( statePath, JSON.stringify({ @@ -602,7 +602,7 @@ describe("Registry", () => { it("rejects non-numeric recovery markers when loading persisted state", async () => { const clock = new FakeClock(1_000); const filesystem = new MemoryFilesystem(); - await filesystem.mkdirp("/home/agent/.pitlane"); + await filesystem.mkdirp("/home/agent/.simlock"); await filesystem.writeFileAtomic( statePath, JSON.stringify({ diff --git a/src/core/registry.ts b/src/core/registry.ts index 00a11b2..533050a 100644 --- a/src/core/registry.ts +++ b/src/core/registry.ts @@ -10,7 +10,7 @@ import { transition, } from "./domain.js"; -const DEFAULT_REGISTRY_PATH = "~/.pitlane/state.json"; +const DEFAULT_REGISTRY_PATH = "~/.simlock/state.json"; export interface RegistryOptions { readonly filesystem: Filesystem; @@ -313,7 +313,7 @@ export class Registry { return cloneDevice(updated); } - /** Flags a device whose provenance marks no longer prove Pitlane owns it. */ + /** Flags a device whose provenance marks no longer prove Simlock owns it. */ async markForeignProvenanceDetected(deviceId: string, at: number): Promise { const { device, index } = this.#requireDeviceRecord(deviceId); if (device.foreignProvenanceDetectedAt !== undefined) { diff --git a/src/core/wait-queue.ts b/src/core/wait-queue.ts index 822515b..c62a5b1 100644 --- a/src/core/wait-queue.ts +++ b/src/core/wait-queue.ts @@ -47,7 +47,7 @@ export class RequesterAlreadyLeasedError extends Error { super( existingLeaseId === undefined ? `Requester already has a lease or pending request: ${requesterId}` - : `Requester ${requesterId} already holds lease ${existingLeaseId}; release it (\`pitlane release ${existingLeaseId}\`) before requesting another device`, + : `Requester ${requesterId} already holds lease ${existingLeaseId}; release it (\`simlock release ${existingLeaseId}\`) before requesting another device`, ); this.name = "RequesterAlreadyLeasedError"; } diff --git a/src/daemon-client/startup-coordinator.ts b/src/daemon-client/startup-coordinator.ts index d47ab36..106849f 100644 --- a/src/daemon-client/startup-coordinator.ts +++ b/src/daemon-client/startup-coordinator.ts @@ -33,7 +33,7 @@ export class DaemonStartupCoordinator { await delay(this.options.clock, this.options.retryIntervalMs ?? 50); } } - throw new Error(`Timed out starting pitlane daemon: ${errorMessage(lastError)}`); + throw new Error(`Timed out starting simlock daemon: ${errorMessage(lastError)}`); } } diff --git a/src/daemon/connection-host.test.ts b/src/daemon/connection-host.test.ts index 53dcaf0..d4960d1 100644 --- a/src/daemon/connection-host.test.ts +++ b/src/daemon/connection-host.test.ts @@ -13,7 +13,7 @@ import { } from "../ports/index.js"; import { DaemonAlreadyRunningError, DaemonEndpointHost } from "./connection-host.js"; -const endpoint = "/pitlane/daemon.sock"; +const endpoint = "/simlock/daemon.sock"; describe("DaemonEndpointHost", () => { it("binds a fresh endpoint and removes only its owned entry on repeated stop", async () => { @@ -22,17 +22,17 @@ describe("DaemonEndpointHost", () => { const host = hostFor(filesystem, ipc, ipc); await host.start(() => undefined); await filesystem.writeFileAtomic(endpoint, "owned"); - await filesystem.writeFileAtomic("/pitlane/other.sock", "unrelated"); + await filesystem.writeFileAtomic("/simlock/other.sock", "unrelated"); await host.stop(); await host.stop(); expect(await filesystem.exists(endpoint)).toBe(false); - expect(await filesystem.exists("/pitlane/other.sock")).toBe(true); + expect(await filesystem.exists("/simlock/other.sock")).toBe(true); await expect(ipc.connect(endpoint)).rejects.toMatchObject({ code: "endpoint-not-found" }); }); it("removes a stale endpoint before binding", async () => { const filesystem = new MemoryFilesystem(); - await filesystem.mkdirp("/pitlane"); + await filesystem.mkdirp("/simlock"); await filesystem.writeFileAtomic(endpoint, "stale"); const ipc = new MemoryIpcTransport(); const host = hostFor(filesystem, ipc, ipc); @@ -43,7 +43,7 @@ describe("DaemonEndpointHost", () => { it("rejects a live endpoint before binding", async () => { const filesystem = new MemoryFilesystem(); - await filesystem.mkdirp("/pitlane"); + await filesystem.mkdirp("/simlock"); await filesystem.writeFileAtomic(endpoint, "live"); const ipc = new MemoryIpcTransport(); const existing = await ipc.listen(endpoint, () => undefined); @@ -82,7 +82,7 @@ describe("DaemonEndpointHost", () => { it("propagates unknown probe failures", async () => { const filesystem = new MemoryFilesystem(); - await filesystem.mkdirp("/pitlane"); + await filesystem.mkdirp("/simlock"); await filesystem.writeFileAtomic(endpoint, "existing"); const connector: IpcConnector = { connect: async (): Promise => { @@ -122,7 +122,7 @@ describe("DaemonEndpointHost", () => { it("logs recovering a stale endpoint before claiming it", async () => { const filesystem = new MemoryFilesystem(); - await filesystem.mkdirp("/pitlane"); + await filesystem.mkdirp("/simlock"); await filesystem.writeFileAtomic(endpoint, "stale"); const ipc = new MemoryIpcTransport(); const sink = new MemoryLogSink(); diff --git a/src/daemon/connection-host.ts b/src/daemon/connection-host.ts index c7f192a..3e11465 100644 --- a/src/daemon/connection-host.ts +++ b/src/daemon/connection-host.ts @@ -17,7 +17,7 @@ export interface ConnectionHost { export class DaemonAlreadyRunningError extends Error { constructor(readonly endpoint: string) { - super(`Pitlane daemon is already running at ${endpoint}`); + super(`Simlock daemon is already running at ${endpoint}`); this.name = "DaemonAlreadyRunningError"; } } diff --git a/src/daemon/main.test.ts b/src/daemon/main.test.ts index 60bd3e2..afac58d 100644 --- a/src/daemon/main.test.ts +++ b/src/daemon/main.test.ts @@ -28,7 +28,7 @@ afterEach(async () => { }); async function start(overrides: Partial = {}) { - const directory = await mkdtemp(join(tmpdir(), "pitlane-main-")); + const directory = await mkdtemp(join(tmpdir(), "simlock-main-")); temporaryDirectories.push(directory); const sink = new MemoryLogSink(); const clock = new FakeClock(1_000); @@ -82,7 +82,7 @@ describe("startDaemon startup readiness", () => { // can prove the socket answers hello/status.get ("starting"), parks lease.request, // and only then converges -- without waiting on a real clock. it("claims the socket and answers hello/status.get while doctor.reconcile is still in flight", async () => { - const directory = await mkdtemp(join(tmpdir(), "pitlane-main-slow-")); + const directory = await mkdtemp(join(tmpdir(), "simlock-main-slow-")); temporaryDirectories.push(directory); const clock = new FakeClock(1_000); const socketPath = join(directory, "daemon.sock"); @@ -166,19 +166,19 @@ describe("discoverDrivers", () => { }); }); -describe("discoverDrivers with PITLANE_DRIVERS_MODULE", () => { - const previousModule = process.env.PITLANE_DRIVERS_MODULE; +describe("discoverDrivers with SIMLOCK_DRIVERS_MODULE", () => { + const previousModule = process.env.SIMLOCK_DRIVERS_MODULE; afterEach(() => { if (previousModule === undefined) { - delete process.env.PITLANE_DRIVERS_MODULE; + delete process.env.SIMLOCK_DRIVERS_MODULE; } else { - process.env.PITLANE_DRIVERS_MODULE = previousModule; + process.env.SIMLOCK_DRIVERS_MODULE = previousModule; } }); async function writeModule(contents: string): Promise { - const directory = await mkdtemp(join(tmpdir(), "pitlane-drivers-module-")); + const directory = await mkdtemp(join(tmpdir(), "simlock-drivers-module-")); temporaryDirectories.push(directory); const modulePath = join(directory, "drivers.mjs"); await writeFile(modulePath, contents, "utf8"); @@ -197,7 +197,7 @@ describe("discoverDrivers with PITLANE_DRIVERS_MODULE", () => { } it("substitutes discovery with the module's createDrivers(context), logging the substitution", async () => { - process.env.PITLANE_DRIVERS_MODULE = await writeModule( + process.env.SIMLOCK_DRIVERS_MODULE = await writeModule( `export function createDrivers(context) { return [{ platform: "ios", fromModule: true, sawContext: typeof context.logger === "object" }]; }`, @@ -210,14 +210,14 @@ describe("discoverDrivers with PITLANE_DRIVERS_MODULE", () => { expect(sink.records).toContainEqual( expect.objectContaining({ level: "info", - message: "Substituting driver discovery via PITLANE_DRIVERS_MODULE", + message: "Substituting driver discovery via SIMLOCK_DRIVERS_MODULE", module: "daemon.driver-discovery", }), ); expect(sink.records).toContainEqual( expect.objectContaining({ level: "info", - message: "Loaded drivers from PITLANE_DRIVERS_MODULE", + message: "Loaded drivers from SIMLOCK_DRIVERS_MODULE", module: "daemon.driver-discovery", fields: expect.objectContaining({ count: 1 }), }), @@ -225,7 +225,7 @@ describe("discoverDrivers with PITLANE_DRIVERS_MODULE", () => { }); it("supports a synchronous createDrivers returning an array directly", async () => { - process.env.PITLANE_DRIVERS_MODULE = await writeModule( + process.env.SIMLOCK_DRIVERS_MODULE = await writeModule( `export function createDrivers() { return []; }`, ); @@ -233,15 +233,15 @@ describe("discoverDrivers with PITLANE_DRIVERS_MODULE", () => { }); it("fails loudly when the module has no createDrivers export", async () => { - process.env.PITLANE_DRIVERS_MODULE = await writeModule(`export const nope = 1;`); + process.env.SIMLOCK_DRIVERS_MODULE = await writeModule(`export const nope = 1;`); await expect(discover(new MemoryLogSink())).rejects.toThrow(/createDrivers/); }); it("fails loudly when the module cannot be imported", async () => { - process.env.PITLANE_DRIVERS_MODULE = join( + process.env.SIMLOCK_DRIVERS_MODULE = join( tmpdir(), - "pitlane-drivers-module-does-not-exist.mjs", + "simlock-drivers-module-does-not-exist.mjs", ); await expect(discover(new MemoryLogSink())).rejects.toThrow(); diff --git a/src/daemon/main.ts b/src/daemon/main.ts index b0bca52..459f372 100644 --- a/src/daemon/main.ts +++ b/src/daemon/main.ts @@ -29,7 +29,7 @@ import { NodeIpcTransport, NodeProcessRunner, NodeSystemStats, - resolvePitlaneHome, + resolveSimlockHome, SystemClock, type SystemStats, type ProcessRunner, @@ -58,7 +58,7 @@ export interface StartDaemonOptions { /** Constructs the daemon's real adapters once; all state remains in the daemon. */ // fallow-ignore-next-line complexity -- explicit production composition necessarily wires all external ports. export async function startDaemon(options: StartDaemonOptions = {}): Promise { - const dataDirectory = options.dataDirectory ?? resolvePitlaneHome(); + const dataDirectory = options.dataDirectory ?? resolveSimlockHome(); const filesystem = options.filesystem ?? new NodeFilesystem(); const clock = options.clock ?? new SystemClock(); const systemStats = options.systemStats ?? new NodeSystemStats(); @@ -129,7 +129,7 @@ export async function startDaemon(options: StartDaemonOptions = {}): Promise { const logger = options.logger.child("driver-discovery"); - const driversModule = process.env.PITLANE_DRIVERS_MODULE; + const driversModule = process.env.SIMLOCK_DRIVERS_MODULE; if (driversModule !== undefined) { return loadDriversModule(driversModule, options, logger); } @@ -218,7 +218,7 @@ export async function discoverDrivers(options: DriverDiscoveryContext): Promise< /** * Testing/advanced hook: substitutes real driver discovery with a module supplied via - * `PITLANE_DRIVERS_MODULE`. The daemon always runs as a separately spawned process, so + * `SIMLOCK_DRIVERS_MODULE`. The daemon always runs as a separately spawned process, so * the module is resolved as a file path (relative to `process.cwd()`) and dynamically * imported -- this is how the e2e suite injects a scriptable fake driver without the * daemon ever knowing it isn't talking to real hardware. A missing module, an import @@ -230,7 +230,7 @@ async function loadDriversModule( context: DriverDiscoveryContext, logger: Logger, ): Promise { - logger.info("Substituting driver discovery via PITLANE_DRIVERS_MODULE", { + logger.info("Substituting driver discovery via SIMLOCK_DRIVERS_MODULE", { module: modulePath, }); const moduleUrl = pathToFileURL(resolve(modulePath)).href; @@ -239,11 +239,11 @@ async function loadDriversModule( }; if (typeof imported.createDrivers !== "function") { throw new Error( - `PITLANE_DRIVERS_MODULE ${modulePath} does not export a createDrivers(context) function`, + `SIMLOCK_DRIVERS_MODULE ${modulePath} does not export a createDrivers(context) function`, ); } const drivers = await imported.createDrivers(context); - logger.info("Loaded drivers from PITLANE_DRIVERS_MODULE", { + logger.info("Loaded drivers from SIMLOCK_DRIVERS_MODULE", { count: drivers.length, module: modulePath, platforms: drivers.map((driver) => driver.platform), @@ -261,7 +261,7 @@ function createFatalLogger(): Logger { clock: new SystemClock(), level: "error", module: "daemon", - sink: new NodeFileLogSink({ path: join(resolvePitlaneHome(), "daemon.log") }), + sink: new NodeFileLogSink({ path: join(resolveSimlockHome(), "daemon.log") }), }); } diff --git a/src/daemon/server.test.ts b/src/daemon/server.test.ts index 39b026e..336f144 100644 --- a/src/daemon/server.test.ts +++ b/src/daemon/server.test.ts @@ -341,7 +341,7 @@ describe("DaemonServer", () => { }); it("recovers a stale socket file and refuses a second live daemon before running any device work", async () => { - const directory = await mkdtemp(join(tmpdir(), "pitlane-stale-")); + const directory = await mkdtemp(join(tmpdir(), "simlock-stale-")); temporaryDirectories.push(directory); const socketPath = join(directory, "daemon.sock"); await new NodeFilesystem().writeFileAtomic(socketPath, "stale"); @@ -1091,7 +1091,7 @@ async function createHarness( } = {}, ) { const directory = - options.socketPath === undefined ? await mkdtemp(join(tmpdir(), "pitlane-daemon-")) : undefined; + options.socketPath === undefined ? await mkdtemp(join(tmpdir(), "simlock-daemon-")) : undefined; if (directory !== undefined) { temporaryDirectories.push(directory); } diff --git a/src/drivers/android/index.test.ts b/src/drivers/android/index.test.ts index adf0044..8431034 100644 --- a/src/drivers/android/index.test.ts +++ b/src/drivers/android/index.test.ts @@ -14,7 +14,7 @@ import { import { AndroidDriver, SdkMissingError } from "./index.js"; const sdk = "/android-sdk"; -const home = "/home/pitlane"; +const home = "/home/simlock"; const avdDirectory = `${home}/.android/avd`; const binaries = { adb: `${sdk}/platform-tools/adb`, @@ -130,7 +130,7 @@ describe("AndroidDriver", () => { "create", "avd", "-n", - "pitlane_first", + "simlock_first", "-k", /.+/, "-d", @@ -140,7 +140,7 @@ describe("AndroidDriver", () => { "create", "avd", "-n", - "pitlane_second", + "simlock_second", "-k", /.+/, "-d", @@ -164,8 +164,8 @@ describe("AndroidDriver", () => { second.provision(spec), ]); - expect(firstDevice.driverData).toMatchObject({ avdName: "pitlane_first", port: 5556 }); - expect(secondDevice.driverData).toMatchObject({ avdName: "pitlane_second", port: 5558 }); + expect(firstDevice.driverData).toMatchObject({ avdName: "simlock_first", port: 5556 }); + expect(secondDevice.driverData).toMatchObject({ avdName: "simlock_second", port: 5558 }); }); it("cold boots without loading or automatically saving snapshots", async () => { @@ -173,10 +173,10 @@ describe("AndroidDriver", () => { await expect(harness.driver.makeReady(harness.device)).resolves.toMatchObject({ address: "emulator-5554", - deviceId: "pitlane_one", + deviceId: "simlock_one", }); expect(harness.runner.calls).toContainEqual({ - args: ["-avd", "pitlane_one", "-port", "5554", "-no-snapshot-save", "-no-snapshot-load"], + args: ["-avd", "simlock_one", "-port", "5554", "-no-snapshot-save", "-no-snapshot-load"], command: binaries.emulator, options: {}, }); @@ -187,18 +187,18 @@ describe("AndroidDriver", () => { await expect(harness.driver.makeReady(harness.device)).resolves.toMatchObject({ address: "emulator-5554", - deviceId: "pitlane_one", + deviceId: "simlock_one", }); expect(harness.runner.calls).toContainEqual({ args: [ "-avd", - "pitlane_one", + "simlock_one", "-port", "5554", "-no-snapshot-save", "-snapshot", - "pitlane_clean_baseline", + "simlock_clean_baseline", ], command: binaries.emulator, options: {}, @@ -256,9 +256,9 @@ describe("AndroidDriver", () => { it("invalidates stale quickboot snapshots and flags the next boot to wipe data", async () => { const harness = await provisionedHarness({ forReclaim: true }); - await harness.filesystem.mkdirp(`${avdDirectory}/pitlane_one.avd/snapshots/default_boot`); + await harness.filesystem.mkdirp(`${avdDirectory}/simlock_one.avd/snapshots/default_boot`); await harness.filesystem.writeFileAtomic( - `${avdDirectory}/pitlane_one.avd/config.ini`, + `${avdDirectory}/simlock_one.avd/config.ini`, "image.sysdir.1=system-images/android-34/google_apis/arm64-v8a\nhw.ramSize=4096\n", ); @@ -268,10 +268,10 @@ describe("AndroidDriver", () => { }); await harness.driver.makeReady(harness.device); await expect( - harness.filesystem.exists(`${avdDirectory}/pitlane_one.avd/snapshots`), + harness.filesystem.exists(`${avdDirectory}/simlock_one.avd/snapshots`), ).resolves.toBe(false); await expect( - harness.filesystem.exists(`${avdDirectory}/pitlane_one.avd/pitlane-clean-baseline.json`), + harness.filesystem.exists(`${avdDirectory}/simlock_one.avd/simlock-clean-baseline.json`), ).resolves.toBe(true); }); @@ -284,7 +284,7 @@ describe("AndroidDriver", () => { expect(harness.runner.calls).toContainEqual({ args: [ "-avd", - "pitlane_one", + "simlock_one", "-port", "5554", "-no-snapshot-save", @@ -306,7 +306,7 @@ describe("AndroidDriver", () => { }); expect(harness.runner.calls).toContainEqual({ - args: ["-s", "emulator-5554", "emu", "avd", "snapshot", "load", "pitlane_clean_baseline"], + args: ["-s", "emulator-5554", "emu", "avd", "snapshot", "load", "simlock_clean_baseline"], command: binaries.adb, options: {}, }); @@ -317,7 +317,7 @@ describe("AndroidDriver", () => { it("tags the baseline with the emulator-normalized post-boot configuration", async () => { const harness = await provisionedHarness({ forBaselineReclaim: true }); await harness.filesystem.writeFileAtomic( - `${avdDirectory}/pitlane_one.avd/config.ini`, + `${avdDirectory}/simlock_one.avd/config.ini`, "hw.ramSize = 2048\n", ); @@ -338,7 +338,7 @@ describe("AndroidDriver", () => { it("reclaims from persisted baseline metadata after a driver restart", async () => { const harness = await provisionedHarness(); await harness.filesystem.writeFileAtomic( - `${avdDirectory}/pitlane_one.avd/config.ini`, + `${avdDirectory}/simlock_one.avd/config.ini`, "hw.ramSize = 2048\n", ); await harness.driver.makeReady(harness.device); @@ -347,7 +347,7 @@ describe("AndroidDriver", () => { processResult(binaries.emulator, ["-version"], "Android emulator version 36.1.9"), processResult( binaries.adb, - ["-s", "emulator-5554", "emu", "avd", "snapshot", "load", "pitlane_clean_baseline"], + ["-s", "emulator-5554", "emu", "avd", "snapshot", "load", "simlock_clean_baseline"], "OK\n", ), processResult( @@ -381,12 +381,12 @@ describe("AndroidDriver", () => { match: { args: [ "-avd", - "pitlane_one", + "simlock_one", "-port", "5554", "-no-snapshot-save", "-snapshot", - "pitlane_clean_baseline", + "simlock_clean_baseline", ], command: binaries.emulator, }, @@ -411,18 +411,18 @@ describe("AndroidDriver", () => { expect(restartedRunner.calls[1]).toMatchObject({ args: [ "-avd", - "pitlane_one", + "simlock_one", "-port", "5554", "-no-snapshot-save", "-snapshot", - "pitlane_clean_baseline", + "simlock_clean_baseline", ], command: binaries.emulator, }); }); - it("shuts down and deletes only the provisioned pitlane AVD", async () => { + it("shuts down and deletes only the provisioned simlock AVD", async () => { const filesystem = await androidFilesystem({ config: "hw.ramSize=2048\n" }); const runner = new ScriptedProcessRunner([ processResult(binaries.avdmanager, ["list", "device"], pixelDevices), @@ -430,7 +430,7 @@ describe("AndroidDriver", () => { "create", "avd", "-n", - "pitlane_delete-me", + "simlock_delete-me", "-k", /.+/, "-d", @@ -442,7 +442,7 @@ describe("AndroidDriver", () => { match: { args: ["-s", "emulator-5554", "emu", "kill"], command: binaries.adb }, result: { code: 1, stderr: "connection refused", stdout: "" }, }, - processResult(binaries.avdmanager, ["delete", "avd", "-n", "pitlane_delete-me"]), + processResult(binaries.avdmanager, ["delete", "avd", "-n", "simlock_delete-me"]), ]); const driver: Driver = await createDriver(filesystem, runner, { ids: ["delete-me"] }); const spec = await driver.resolveSpec( @@ -453,7 +453,7 @@ describe("AndroidDriver", () => { await expect(driver.destroy(device)).resolves.toBeUndefined(); expect(runner.calls.at(-1)).toMatchObject({ - args: ["delete", "avd", "-n", "pitlane_delete-me"], + args: ["delete", "avd", "-n", "simlock_delete-me"], command: binaries.avdmanager, }); }); @@ -518,8 +518,8 @@ describe("AndroidDriver", () => { it("joins adb devices with getprop to compute runState per AVD: running, stopped, transitioning", async () => { const filesystem = await androidFilesystem(); - await filesystem.mkdirp(`${avdDirectory}/pitlane_running.avd`); - await filesystem.mkdirp(`${avdDirectory}/pitlane_stopped.avd`); + await filesystem.mkdirp(`${avdDirectory}/simlock_running.avd`); + await filesystem.mkdirp(`${avdDirectory}/simlock_stopped.avd`); const runner = new ScriptedProcessRunner([ processResult(binaries.adb, ["devices"], "List of devices attached\nemulator-5554\tdevice\n"), processResult( @@ -528,9 +528,9 @@ describe("AndroidDriver", () => { "-s", "emulator-5554", "shell", - "getprop ro.boot.qemu.avd_name; cat /data/local/tmp/pitlane-mark.json 2>/dev/null || true", + "getprop ro.boot.qemu.avd_name; cat /data/local/tmp/simlock-mark.json 2>/dev/null || true", ], - "pitlane_running\n", + "simlock_running\n", ), ]); const driver = await createDriver(filesystem, runner); @@ -542,15 +542,15 @@ describe("AndroidDriver", () => { .map((device) => ({ deviceId: device.deviceId, runState: device.runState })) .sort((left, right) => left.deviceId.localeCompare(right.deviceId)), ).toEqual([ - { deviceId: "pitlane_running", runState: "running" }, - { deviceId: "pitlane_stopped", runState: "stopped" }, + { deviceId: "simlock_running", runState: "running" }, + { deviceId: "simlock_stopped", runState: "stopped" }, ]); - expect(reality.processes).toEqual([expect.objectContaining({ deviceId: "pitlane_running" })]); + expect(reality.processes).toEqual([expect.objectContaining({ deviceId: "simlock_running" })]); }); it("treats an otherwise-stopped AVD as transitioning when an unattributable transitional serial is present", async () => { const filesystem = await androidFilesystem(); - await filesystem.mkdirp(`${avdDirectory}/pitlane_idle.avd`); + await filesystem.mkdirp(`${avdDirectory}/simlock_idle.avd`); const runner = new ScriptedProcessRunner([ processResult( binaries.adb, @@ -563,7 +563,7 @@ describe("AndroidDriver", () => { const reality = await driver.listManaged(); expect(reality.devices).toEqual([ - expect.objectContaining({ deviceId: "pitlane_idle", runState: "transitioning" }), + expect.objectContaining({ deviceId: "simlock_idle", runState: "transitioning" }), ]); // Only settled `device`-state serials are counted as running processes; the // unattributable offline serial never appears here regardless of the AVD fallback above. @@ -572,7 +572,7 @@ describe("AndroidDriver", () => { it("still resolves a stopped AVD as stopped when adb reports no serials at all", async () => { const filesystem = await androidFilesystem(); - await filesystem.mkdirp(`${avdDirectory}/pitlane_idle.avd`); + await filesystem.mkdirp(`${avdDirectory}/simlock_idle.avd`); const runner = new ScriptedProcessRunner([ processResult(binaries.adb, ["devices"], "List of devices attached\n"), ]); @@ -581,7 +581,7 @@ describe("AndroidDriver", () => { const reality = await driver.listManaged(); expect(reality.devices).toEqual([ - expect.objectContaining({ deviceId: "pitlane_idle", runState: "stopped" }), + expect.objectContaining({ deviceId: "simlock_idle", runState: "stopped" }), ]); }); @@ -594,7 +594,7 @@ describe("AndroidDriver", () => { "create", "avd", "-n", - "pitlane_one", + "simlock_one", "-k", /.+/, "-d", @@ -628,9 +628,9 @@ describe("AndroidDriver", () => { await driver.makeReady(device); await driver.makeReady(device); - const config = await filesystem.readFile(`${avdDirectory}/pitlane_one.avd/config.ini`); - expect(config.split(/\r?\n/).filter((line) => line.startsWith("pitlane.mark="))).toEqual([ - "pitlane.mark=device-3", + const config = await filesystem.readFile(`${avdDirectory}/simlock_one.avd/config.ini`); + expect(config.split(/\r?\n/).filter((line) => line.startsWith("simlock.mark="))).toEqual([ + "simlock.mark=device-3", ]); expect(config).toContain("hw.ramSize=2048"); }); @@ -641,8 +641,8 @@ describe("AndroidDriver", () => { await harness.driver.reclaim(harness.device, { clean: "standard" }); - const config = await harness.filesystem.readFile(`${avdDirectory}/pitlane_one.avd/config.ini`); - expect(config).toContain("pitlane.mark=device-3"); + const config = await harness.filesystem.readFile(`${avdDirectory}/simlock_one.avd/config.ini`); + expect(config).toContain("simlock.mark=device-3"); expect(harness.runner.calls).toContainEqual( expect.objectContaining({ args: markWriteExpectation("emulator-5554", "device-3").match.args, @@ -650,10 +650,10 @@ describe("AndroidDriver", () => { ); }); - it("does not change the config hash when pitlane.mark is present in config.ini", async () => { + it("does not change the config hash when simlock.mark is present in config.ini", async () => { const withoutMark = await androidFilesystem({ config: "hw.ramSize=2048\n" }); const withMark = await androidFilesystem({ - config: "hw.ramSize=2048\npitlane.mark=some-token\n", + config: "hw.ramSize=2048\nsimlock.mark=some-token\n", }); const buildExpectations = (): ScriptedProcessExpectation[] => [ processResult(binaries.avdmanager, ["list", "device"], pixelDevices), @@ -661,7 +661,7 @@ describe("AndroidDriver", () => { "create", "avd", "-n", - "pitlane_one", + "simlock_one", "-k", /.+/, "-d", @@ -694,7 +694,7 @@ describe("AndroidDriver", () => { it("listManaged reports matching durable and erasable marks for a running device", async () => { const filesystem = await androidFilesystem({ - config: "hw.ramSize=2048\npitlane.mark=tok-123\n", + config: "hw.ramSize=2048\nsimlock.mark=tok-123\n", }); const runner = new ScriptedProcessRunner([ processResult(binaries.adb, ["devices"], "List of devices attached\nemulator-5554\tdevice\n"), @@ -704,16 +704,16 @@ describe("AndroidDriver", () => { "-s", "emulator-5554", "shell", - "getprop ro.boot.qemu.avd_name; cat /data/local/tmp/pitlane-mark.json 2>/dev/null || true", + "getprop ro.boot.qemu.avd_name; cat /data/local/tmp/simlock-mark.json 2>/dev/null || true", ], - 'pitlane_one\n{"token":"tok-123"}', + 'simlock_one\n{"token":"tok-123"}', ), ]); const driver = await createDriver(filesystem, runner); const reality = await driver.listManaged(); - const device = reality.devices.find((candidate) => candidate.deviceId === "pitlane_one"); + const device = reality.devices.find((candidate) => candidate.deviceId === "simlock_one"); expect(device?.mark).toEqual({ durable: "tok-123", erasable: "tok-123", @@ -723,7 +723,7 @@ describe("AndroidDriver", () => { it("listManaged reports an erased running device when the erasable mark file is gone", async () => { const filesystem = await androidFilesystem({ - config: "hw.ramSize=2048\npitlane.mark=tok-123\n", + config: "hw.ramSize=2048\nsimlock.mark=tok-123\n", }); const runner = new ScriptedProcessRunner([ processResult(binaries.adb, ["devices"], "List of devices attached\nemulator-5554\tdevice\n"), @@ -733,16 +733,16 @@ describe("AndroidDriver", () => { "-s", "emulator-5554", "shell", - "getprop ro.boot.qemu.avd_name; cat /data/local/tmp/pitlane-mark.json 2>/dev/null || true", + "getprop ro.boot.qemu.avd_name; cat /data/local/tmp/simlock-mark.json 2>/dev/null || true", ], - "pitlane_one\n", + "simlock_one\n", ), ]); const driver = await createDriver(filesystem, runner); const reality = await driver.listManaged(); - const device = reality.devices.find((candidate) => candidate.deviceId === "pitlane_one"); + const device = reality.devices.find((candidate) => candidate.deviceId === "simlock_one"); expect(device?.mark).toEqual({ durable: "tok-123", erasable: undefined, @@ -752,7 +752,7 @@ describe("AndroidDriver", () => { it("keeps listManaged alive when a serial dies between the scan and the read", async () => { const filesystem = await androidFilesystem({ - config: "hw.ramSize=2048\npitlane.mark=tok-123\n", + config: "hw.ramSize=2048\nsimlock.mark=tok-123\n", }); const runner = new ScriptedProcessRunner([ processResult(binaries.adb, ["devices"], "List of devices attached\nemulator-5554\tdevice\n"), @@ -762,7 +762,7 @@ describe("AndroidDriver", () => { "-s", "emulator-5554", "shell", - "getprop ro.boot.qemu.avd_name; cat /data/local/tmp/pitlane-mark.json 2>/dev/null || true", + "getprop ro.boot.qemu.avd_name; cat /data/local/tmp/simlock-mark.json 2>/dev/null || true", ], command: binaries.adb, }, @@ -775,7 +775,7 @@ describe("AndroidDriver", () => { // reality view over one dead serial would strand every other managed device. const reality = await driver.listManaged(); - const device = reality.devices.find((candidate) => candidate.deviceId === "pitlane_one"); + const device = reality.devices.find((candidate) => candidate.deviceId === "simlock_one"); expect(device?.runState).toBe("transitioning"); // The durable half is a host file and stays readable; the erasable half genuinely // was not read, so it must report unreadable rather than absent -- absent would @@ -789,7 +789,7 @@ describe("AndroidDriver", () => { it("listManaged reports erasableReadable: false for a stopped, marked device", async () => { const filesystem = await androidFilesystem({ - config: "hw.ramSize=2048\npitlane.mark=tok-123\n", + config: "hw.ramSize=2048\nsimlock.mark=tok-123\n", }); const runner = new ScriptedProcessRunner([ processResult(binaries.adb, ["devices"], "List of devices attached\n"), @@ -798,7 +798,7 @@ describe("AndroidDriver", () => { const reality = await driver.listManaged(); - const device = reality.devices.find((candidate) => candidate.deviceId === "pitlane_one"); + const device = reality.devices.find((candidate) => candidate.deviceId === "simlock_one"); expect(device?.mark).toEqual({ durable: "tok-123", erasable: undefined, @@ -808,7 +808,7 @@ describe("AndroidDriver", () => { it("listManaged reports no mark for a stopped, pre-existing AVD with no durable key (upgrade path)", async () => { const filesystem = await androidFilesystem(); - await filesystem.mkdirp(`${avdDirectory}/pitlane_legacy.avd`); + await filesystem.mkdirp(`${avdDirectory}/simlock_legacy.avd`); const runner = new ScriptedProcessRunner([ processResult(binaries.adb, ["devices"], "List of devices attached\n"), ]); @@ -816,12 +816,12 @@ describe("AndroidDriver", () => { const reality = await driver.listManaged(); - const device = reality.devices.find((candidate) => candidate.deviceId === "pitlane_legacy"); + const device = reality.devices.find((candidate) => candidate.deviceId === "simlock_legacy"); expect(device?.mark).toBeUndefined(); }); }); -const live = process.env.PITLANE_LIVE_ANDROID === "1" ? it : it.skip; +const live = process.env.SIMLOCK_LIVE_ANDROID === "1" ? it : it.skip; live( "live smoke: provision, quickboot reclaim, re-ready, and destroy", @@ -835,11 +835,11 @@ live( }); const spec = await driver.resolveSpec( { - model: process.env.PITLANE_LIVE_ANDROID_MODEL ?? "Pixel 8", + model: process.env.SIMLOCK_LIVE_ANDROID_MODEL ?? "Pixel 8", platform: "android", - ...(process.env.PITLANE_LIVE_ANDROID_API === undefined + ...(process.env.SIMLOCK_LIVE_ANDROID_API === undefined ? {} - : { osVersion: process.env.PITLANE_LIVE_ANDROID_API }), + : { osVersion: process.env.SIMLOCK_LIVE_ANDROID_API }), }, { allowDownload: false }, ); @@ -880,7 +880,7 @@ async function provisionedHarness( "create", "avd", "-n", - "pitlane_one", + "simlock_one", "-k", /.+/, "-d", @@ -926,7 +926,7 @@ async function provisionedHarness( processResult(binaries.emulator, ["-version"], "Android emulator version 36.1.9"), processResult( binaries.adb, - ["-s", "emulator-5554", "emu", "avd", "snapshot", "load", "pitlane_clean_baseline"], + ["-s", "emulator-5554", "emu", "avd", "snapshot", "load", "simlock_clean_baseline"], "OK\n", ), processResult( @@ -1002,8 +1002,8 @@ async function androidFilesystem( await filesystem.mkdirp(`${sdk}/system-images/android-${api}/${tag}/${abi}`); } if (options.config !== undefined) { - await filesystem.mkdirp(`${avdDirectory}/pitlane_one.avd`); - await filesystem.writeFileAtomic(`${avdDirectory}/pitlane_one.avd/config.ini`, options.config); + await filesystem.mkdirp(`${avdDirectory}/simlock_one.avd`); + await filesystem.writeFileAtomic(`${avdDirectory}/simlock_one.avd/config.ini`, options.config); } return filesystem; } @@ -1018,7 +1018,7 @@ function baselineBuildExpectations(options: { { hangs: bootCompleted.trim() !== "1", match: { - args: ["-avd", "pitlane_one", "-port", "5554", "-no-snapshot-save", ...options.launchArgs], + args: ["-avd", "simlock_one", "-port", "5554", "-no-snapshot-save", ...options.launchArgs], command: binaries.emulator, }, }, @@ -1056,12 +1056,12 @@ function baselineBuildExpectations(options: { "avd", "snapshot", "save", - "pitlane_clean_baseline", + "simlock_clean_baseline", ]), processResult( binaries.adb, ["-s", "emulator-5554", "emu", "avd", "snapshot", "list"], - "pitlane_clean_baseline\n", + "simlock_clean_baseline\n", ), processResult(binaries.emulator, ["-version"], "Android emulator version 36.1.9"), processResult(binaries.adb, ["-s", "emulator-5554", "emu", "kill"]), @@ -1070,12 +1070,12 @@ function baselineBuildExpectations(options: { match: { args: [ "-avd", - "pitlane_one", + "simlock_one", "-port", "5554", "-no-snapshot-save", "-snapshot", - "pitlane_clean_baseline", + "simlock_clean_baseline", ], command: binaries.emulator, }, @@ -1107,6 +1107,6 @@ function markWriteExpectation(serial: string, token: string): ScriptedProcessExp "-s", serial, "shell", - `echo '${JSON.stringify({ token })}' > /data/local/tmp/pitlane-mark.json`, + `echo '${JSON.stringify({ token })}' > /data/local/tmp/simlock-mark.json`, ]); } diff --git a/src/drivers/android/index.ts b/src/drivers/android/index.ts index 3faba6a..fef9517 100644 --- a/src/drivers/android/index.ts +++ b/src/drivers/android/index.ts @@ -34,9 +34,9 @@ const SDK_DOWNLOAD_TIMEOUT_MS = 20 * 60_000; // from ever turning a "we already killed it" cleanup into an unbounded await. const SIGKILL_REAP_TIMEOUT_MS = 5_000; const SNAPSHOT_BOOT_ESTIMATE_MS = 4_000; -const CLEAN_BASELINE = "pitlane_clean_baseline"; -const DURABLE_MARK_KEY = "pitlane.mark"; -const ERASABLE_MARK_PATH = "/data/local/tmp/pitlane-mark.json"; +const CLEAN_BASELINE = "simlock_clean_baseline"; +const DURABLE_MARK_KEY = "simlock.mark"; +const ERASABLE_MARK_PATH = "/data/local/tmp/simlock-mark.json"; export interface AndroidDriverOptions { readonly clock: Clock; @@ -166,7 +166,7 @@ export class AndroidDriver implements Driver { this.#assertAndroidSpec(spec); const profile = await this.#profileFor(spec.model); const image = await this.#requireImage(spec.osVersion); - const avdName = `pitlane_${this.#idGenerator.generate()}`; + const avdName = `simlock_${this.#idGenerator.generate()}`; const packageName = systemImagePackage(image.apiLevel, image.tag, image.abi); await this.#runOrThrow(this.#sdk.avdmanager, [ @@ -364,7 +364,7 @@ export class AndroidDriver implements Driver { const avdNames: string[] = []; if (await this.#filesystem.exists(this.#avdDirectory)) { for (const entry of await this.#filesystem.readdir(this.#avdDirectory)) { - const match = /^(pitlane_.+)\.avd$/.exec(entry); + const match = /^(simlock_.+)\.avd$/.exec(entry); if (match?.[1] === undefined) continue; avdNames.push(match[1]); } @@ -433,7 +433,7 @@ export class AndroidDriver implements Driver { } const [nameLine = "", ...markLines] = output.stdout.split(/\r?\n/); const avdName = nameLine.trim(); - if (!avdName.startsWith("pitlane_")) continue; + if (!avdName.startsWith("simlock_")) continue; runningByAvdName.add(avdName); erasableMarkByAvdName.set(avdName, parseErasableMark(markLines.join("\n"))); const port = Number(candidate.slice("emulator-".length)); @@ -595,8 +595,8 @@ export class AndroidDriver implements Driver { /** * Writes the same provenance token into both regions of the mark: the durable - * `pitlane.mark` key in `config.ini` (host-side, survives an erase) and the erasable - * `/data/local/tmp/pitlane-mark.json` file on the device (destroyed by an erase). Must be + * `simlock.mark` key in `config.ini` (host-side, survives an erase) and the erasable + * `/data/local/tmp/simlock-mark.json` file on the device (destroyed by an erase). Must be * called after every readiness transition -- see the call sites in `makeReady` and * `reclaim` for why "the tail of `makeReady`" alone is not sufficient. */ @@ -769,7 +769,7 @@ export class AndroidDriver implements Driver { } #baselineMetadataPath(avdName: string): string { - return `${this.#avdDirectory}/${avdName}.avd/pitlane-clean-baseline.json`; + return `${this.#avdDirectory}/${avdName}.avd/simlock-clean-baseline.json`; } async #shutdown(data: AndroidDriverData, state: DeviceState): Promise { diff --git a/src/drivers/ios/fixtures/simctl-list-devices.json b/src/drivers/ios/fixtures/simctl-list-devices.json index 38951a0..2d892e8 100644 --- a/src/drivers/ios/fixtures/simctl-list-devices.json +++ b/src/drivers/ios/fixtures/simctl-list-devices.json @@ -2,31 +2,31 @@ "devices": { "com.apple.CoreSimulator.SimRuntime.iOS-26-5": [ { - "name": "pitlane-booted", + "name": "simlock-booted", "udid": "00000000-0000-0000-0000-000000000101", "state": "Booted", "dataPath": "/Devices/00000000-0000-0000-0000-000000000101/data" }, { - "name": "pitlane-shutdown", + "name": "simlock-shutdown", "udid": "00000000-0000-0000-0000-000000000102", "state": "Shutdown", "dataPath": "/Devices/00000000-0000-0000-0000-000000000102/data" }, { - "name": "pitlane-booting", + "name": "simlock-booting", "udid": "00000000-0000-0000-0000-000000000103", "state": "Booting", "dataPath": "/Devices/00000000-0000-0000-0000-000000000103/data" }, { - "name": "pitlane-shutting-down", + "name": "simlock-shutting-down", "udid": "00000000-0000-0000-0000-000000000104", "state": "Shutting Down", "dataPath": "/Devices/00000000-0000-0000-0000-000000000104/data" }, { - "name": "Not Pitlane", + "name": "Not Simlock", "udid": "00000000-0000-0000-0000-000000000105", "state": "Booted", "dataPath": "/Devices/00000000-0000-0000-0000-000000000105/data" diff --git a/src/drivers/ios/index.test.ts b/src/drivers/ios/index.test.ts index e7118e4..6350439 100644 --- a/src/drivers/ios/index.test.ts +++ b/src/drivers/ios/index.test.ts @@ -35,13 +35,13 @@ const listDevicesInvocation = { const spec = { model: "iPhone 16", osVersion: "26.5", platform: "ios" } as const; const driverData = { deviceTypeId: "com.apple.CoreSimulator.SimDeviceType.iPhone-16", - name: "pitlane-device-1", + name: "simlock-device-1", runtimeId: "com.apple.CoreSimulator.SimRuntime.iOS-26-5", udid: "00000000-0000-0000-0000-000000000001", } as const; const dataPath = `/Devices/${driverData.udid}/data`; -const durableMarkPath = `/Devices/${driverData.udid}/pitlane-mark.json`; -const erasableMarkPath = `${dataPath}/pitlane-mark.json`; +const durableMarkPath = `/Devices/${driverData.udid}/simlock-mark.json`; +const erasableMarkPath = `${dataPath}/simlock-mark.json`; function deviceListResponse(state: string): string { return JSON.stringify({ @@ -135,7 +135,7 @@ describe("IosSimctlDriver", () => { args: [ "simctl", "create", - "pitlane-device-1", + "simlock-device-1", driverData.deviceTypeId, driverData.runtimeId, ], @@ -166,7 +166,7 @@ describe("IosSimctlDriver", () => { args: [ "simctl", "create", - "pitlane-device-1", + "simlock-device-1", driverData.deviceTypeId, driverData.runtimeId, ], @@ -189,7 +189,7 @@ describe("IosSimctlDriver", () => { args: [ "simctl", "create", - "pitlane-device-1", + "simlock-device-1", driverData.deviceTypeId, driverData.runtimeId, ], @@ -313,7 +313,7 @@ describe("IosSimctlDriver", () => { args: [ "simctl", "create", - "pitlane-device-1", + "simlock-device-1", driverData.deviceTypeId, driverData.runtimeId, ], @@ -398,7 +398,7 @@ describe("IosSimctlDriver", () => { expect(runner.calls).toEqual([{ ...listInvocation, options: { timeoutMs: 30_000 } }]); }); - it("maps simctl device state to runState and filters to pitlane- devices", async () => { + it("maps simctl device state to runState and filters to simlock- devices", async () => { const runner = new ScriptedProcessRunner([ { match: listDevicesInvocation, result: { code: 0, stderr: "", stdout: listDevicesFixture } }, ]); @@ -535,7 +535,7 @@ describe("IosSimctlDriver", () => { expect(reality.devices[0]?.mark).toBeUndefined(); }); - it.skipIf(process.env.PITLANE_LIVE_IOS !== "1")( + it.skipIf(process.env.SIMLOCK_LIVE_IOS !== "1")( "runs a provision-to-destroy smoke test against simctl", async () => { const driver = new IosSimctlDriver({ diff --git a/src/drivers/ios/index.ts b/src/drivers/ios/index.ts index ecde767..945fb41 100644 --- a/src/drivers/ios/index.ts +++ b/src/drivers/ios/index.ts @@ -23,7 +23,7 @@ import type { const COMMAND_TIMEOUT_MS = 30_000; const BOOTSTATUS_TIMEOUT_MS = 120_000; -const MARK_FILE_NAME = "pitlane-mark.json"; +const MARK_FILE_NAME = "simlock-mark.json"; interface IosDriverData { readonly deviceTypeId: string; @@ -119,7 +119,7 @@ export class IosSimctlDriver implements Driver { async provision(spec: DeviceSpec): Promise { this.#requireIosPlatform(spec.platform); const resolved = await this.#resolvedSpec(spec); - const name = `pitlane-${this.#idGenerator.generate()}`; + const name = `simlock-${this.#idGenerator.generate()}`; const result = await this.#simctl( ["create", name, resolved.deviceType.identifier, resolved.runtime.identifier], COMMAND_TIMEOUT_MS, @@ -478,7 +478,7 @@ function parseManagedDevices(value: unknown): ParsedManagedDevice[] { if (!isRecord(device) || typeof device.name !== "string" || typeof device.udid !== "string") { continue; } - if (!device.name.startsWith("pitlane-")) continue; + if (!device.name.startsWith("simlock-")) continue; devices.push({ dataPath: typeof device.dataPath === "string" ? device.dataPath : undefined, name: device.name, diff --git a/src/index.test.ts b/src/index.test.ts index e5c6ac3..11b27f6 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -4,6 +4,6 @@ import { projectName } from "./index.js"; describe("projectName", () => { it("identifies the project", () => { - expect(projectName).toBe("pitlane"); + expect(projectName).toBe("simlock"); }); }); diff --git a/src/index.ts b/src/index.ts index 9653aa9..67aeae2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1 +1 @@ -export const projectName = "pitlane"; +export const projectName = "simlock"; diff --git a/src/mcp/main.test.ts b/src/mcp/main.test.ts index 7fad03d..34084e9 100644 --- a/src/mcp/main.test.ts +++ b/src/mcp/main.test.ts @@ -91,13 +91,13 @@ describe("MCP stdio lifecycle", () => { await client.close(); }); - it("sources the requester id from PITLANE_AGENT_ID when none is given explicitly", async () => { + it("sources the requester id from SIMLOCK_AGENT_ID when none is given explicitly", async () => { const connection = new LeaseConnection(); const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair(); const runner = await startMcpStdio({ connect: async () => connection, createTransport: () => serverTransport, - env: { PITLANE_AGENT_ID: "agent-from-env" }, + env: { SIMLOCK_AGENT_ID: "agent-from-env" }, signals: new FakeSignals(), }); const client = new Client({ name: "test", version: "1.0.0" }); @@ -115,13 +115,13 @@ describe("MCP stdio lifecycle", () => { await client.close(); }); - it("prefers an explicit requesterId over PITLANE_AGENT_ID", async () => { + it("prefers an explicit requesterId over SIMLOCK_AGENT_ID", async () => { const connection = new LeaseConnection(); const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair(); const runner = await startMcpStdio({ connect: async () => connection, createTransport: () => serverTransport, - env: { PITLANE_AGENT_ID: "agent-from-env" }, + env: { SIMLOCK_AGENT_ID: "agent-from-env" }, requesterId: "explicit-agent", signals: new FakeSignals(), }); diff --git a/src/mcp/main.ts b/src/mcp/main.ts index f0a9047..106006b 100644 --- a/src/mcp/main.ts +++ b/src/mcp/main.ts @@ -6,7 +6,7 @@ import { fileURLToPath } from "node:url"; import { NodeDaemonLauncher, NodeIpcTransport, - resolvePitlaneHome, + resolveSimlockHome, SystemClock, } from "../ports/index.js"; import { connectDaemon } from "../daemon-client/client.js"; @@ -35,7 +35,7 @@ export interface McpStdioEnvironment { readonly connect?: () => Promise; readonly createServer?: (session: McpSession) => McpServer; readonly createTransport?: () => McpTransport; - /** Source for `PITLANE_AGENT_ID` when `requesterId` is not given explicitly. */ + /** Source for `SIMLOCK_AGENT_ID` when `requesterId` is not given explicitly. */ readonly env?: NodeJS.ProcessEnv; readonly requesterId?: string; readonly signals?: Signals; @@ -66,7 +66,7 @@ export async function startMcpStdio( const env = environment.env ?? process.env; const session = new McpSession({ connect: environment.connect ?? defaults.connect, - requesterId: environment.requesterId ?? env.PITLANE_AGENT_ID ?? `mcp:${process.pid}`, + requesterId: environment.requesterId ?? env.SIMLOCK_AGENT_ID ?? `mcp:${process.pid}`, }); const server = (environment.createServer ?? createMcpServer)(session); const transport = (environment.createTransport ?? defaults.createTransport)(); @@ -138,7 +138,7 @@ export async function startMcpStdio( } function defaultEnvironment(): Required> { - const dataDirectory = resolvePitlaneHome(); + const dataDirectory = resolveSimlockHome(); const clock = new SystemClock(); const ipc = new NodeIpcTransport(); const socketPath = join(dataDirectory, "daemon.sock"); diff --git a/src/mcp/server.test.ts b/src/mcp/server.test.ts index 2bfd7bb..f9aeda6 100644 --- a/src/mcp/server.test.ts +++ b/src/mcp/server.test.ts @@ -141,11 +141,11 @@ describe("MCP server", () => { data: { device_id: "SIM-1", lease_id: "lease-1", - message: "Pitlane lease ended; this session no longer holds the device.", + message: "Simlock lease ended; this session no longer holds the device.", reason: "expired", }, level: "warning", - logger: "pitlane", + logger: "simlock", }, ]); @@ -314,7 +314,7 @@ describe("MCP server", () => { }); expect(JSON.parse(text(unexpected))).toEqual({ code: "INTERNAL", - message: "Pitlane could not complete the request", + message: "Simlock could not complete the request", }); expect(text(unexpected)).not.toContain("private"); } finally { diff --git a/src/mcp/server.ts b/src/mcp/server.ts index 4e8dcf1..7ca2fbd 100644 --- a/src/mcp/server.ts +++ b/src/mcp/server.ts @@ -18,7 +18,7 @@ import { toMcpErrorResult, } from "./session.js"; -const SERVER_INFO = { name: "pitlane", version: "1.0.0" }; +const SERVER_INFO = { name: "simlock", version: "1.0.0" }; /** * Progress is reported on a 3-stage scale (queued / provisioning-or-reclaiming / booting), each @@ -180,11 +180,11 @@ export function createMcpServer(session: McpSession): McpServer { data: { device_id: notice.deviceId, lease_id: notice.leaseId, - message: "Pitlane lease ended; this session no longer holds the device.", + message: "Simlock lease ended; this session no longer holds the device.", reason: notice.reason, }, level: "warning", - logger: "pitlane", + logger: "simlock", }); }); @@ -197,13 +197,13 @@ export function createMcpServer(session: McpSession): McpServer { /** * Whatever this session had running inside the device (a launched app, a log stream, an - * Appium/XCUITest session, a port forward) died with the crash and pitlane cannot restore it -- + * Appium/XCUITest session, a port forward) died with the crash and simlock cannot restore it -- * the lease survives, but the agent still needs to know its in-device state is gone. */ function deviceHealthLoggingMessage(notice: DeviceHealthNotice): { readonly data: Record; readonly level: "info" | "warning"; - readonly logger: "pitlane"; + readonly logger: "simlock"; } { if (notice.kind === "unhealthy") { return { @@ -211,11 +211,11 @@ function deviceHealthLoggingMessage(notice: DeviceHealthNotice): { device_id: notice.deviceId, lease_id: notice.leaseId, message: - "Pitlane's device crashed outside pitlane; anything running inside it (apps, log streams, automation sessions, port forwards) is gone. Recovery is in progress under the same lease.", + "Simlock's device crashed outside simlock; anything running inside it (apps, log streams, automation sessions, port forwards) is gone. Recovery is in progress under the same lease.", reason: notice.reason, }, level: "warning", - logger: "pitlane", + logger: "simlock", }; } return { @@ -223,10 +223,10 @@ function deviceHealthLoggingMessage(notice: DeviceHealthNotice): { attempts: notice.attempts, device_id: notice.deviceId, lease_id: notice.leaseId, - message: "Pitlane's device was rebooted and is ready again under the same lease.", + message: "Simlock's device was rebooted and is ready again under the same lease.", }, level: "info", - logger: "pitlane", + logger: "simlock", }; } diff --git a/src/mcp/session.test.ts b/src/mcp/session.test.ts index 6cdf676..3ed5b55 100644 --- a/src/mcp/session.test.ts +++ b/src/mcp/session.test.ts @@ -128,7 +128,7 @@ describe("McpSession", () => { }); expect(toMcpErrorResult(new Error("/private/secret"))).toEqual({ code: "INTERNAL", - message: "Pitlane could not complete the request", + message: "Simlock could not complete the request", }); }); diff --git a/src/mcp/session.ts b/src/mcp/session.ts index 9c32e27..95e2550 100644 --- a/src/mcp/session.ts +++ b/src/mcp/session.ts @@ -95,7 +95,7 @@ export function toMcpErrorResult(error: unknown): McpErrorResult { if (error instanceof DaemonClientError || error instanceof McpSessionError) { return { code: error.code, message: error.message }; } - return { code: "INTERNAL", message: "Pitlane could not complete the request" }; + return { code: "INTERNAL", message: "Simlock could not complete the request" }; } export class McpSession { diff --git a/src/ports/daemon-launcher.test.ts b/src/ports/daemon-launcher.test.ts index 9cfbefb..4fb6d78 100644 --- a/src/ports/daemon-launcher.test.ts +++ b/src/ports/daemon-launcher.test.ts @@ -19,7 +19,7 @@ describe("FakeDaemonLauncher", () => { describe("NodeDaemonLauncher", () => { it("rejects asynchronous spawn failures", async () => { - const directory = await mkdtemp(join(tmpdir(), "pitlane-launcher-")); + const directory = await mkdtemp(join(tmpdir(), "simlock-launcher-")); try { await expect( new NodeDaemonLauncher({ @@ -34,7 +34,7 @@ describe("NodeDaemonLauncher", () => { }); it("combines stdout and stderr in an append-only log", async () => { - const directory = await mkdtemp(join(tmpdir(), "pitlane-launcher-")); + const directory = await mkdtemp(join(tmpdir(), "simlock-launcher-")); const logPath = join(directory, "daemon.log"); const launcher = new NodeDaemonLauncher({ args: ["-e", "console.log('out'); console.error('err')"], diff --git a/src/ports/daemon-launcher.ts b/src/ports/daemon-launcher.ts index a36b484..89d3f6f 100644 --- a/src/ports/daemon-launcher.ts +++ b/src/ports/daemon-launcher.ts @@ -23,7 +23,7 @@ export class NodeDaemonLauncher implements DaemonLauncher { const child = spawn(this.options.command, this.options.args, { detached: true, // Explicit rather than relying on spawn's default: the daemon must inherit - // overrides like PITLANE_HOME and PITLANE_DRIVERS_MODULE from whichever + // overrides like SIMLOCK_HOME and SIMLOCK_DRIVERS_MODULE from whichever // frontend (CLI/MCP) auto-launched it. env: process.env, stdio: ["ignore", log.fd, log.fd], diff --git a/src/ports/filesystem.test.ts b/src/ports/filesystem.test.ts index a7f0f36..b70a102 100644 --- a/src/ports/filesystem.test.ts +++ b/src/ports/filesystem.test.ts @@ -2,7 +2,7 @@ import { afterEach, describe, expect, it } from "vitest"; import { type Filesystem, MemoryFilesystem, NodeFilesystem } from "./index.js"; -const temporaryDirectory = `${process.cwd()}/.pitlane-ports-test`; +const temporaryDirectory = `${process.cwd()}/.simlock-ports-test`; const implementations: Array<{ name: string; diff --git a/src/ports/index.ts b/src/ports/index.ts index 4158cb2..ff9cec2 100644 --- a/src/ports/index.ts +++ b/src/ports/index.ts @@ -1,5 +1,5 @@ export { type Filesystem, MemoryFilesystem, NodeFilesystem } from "./filesystem.js"; -export { resolvePitlaneHome } from "./paths.js"; +export { resolveSimlockHome } from "./paths.js"; export { type DaemonLauncher, FakeDaemonLauncher, NodeDaemonLauncher } from "./daemon-launcher.js"; export { type IpcConnection, diff --git a/src/ports/ipc.test.ts b/src/ports/ipc.test.ts index f1df91f..167f46d 100644 --- a/src/ports/ipc.test.ts +++ b/src/ports/ipc.test.ts @@ -44,7 +44,7 @@ describe("MemoryIpcTransport", () => { describe("NodeIpcTransport", () => { it("connects, exchanges data, closes, and normalizes setup failures", async () => { - const directory = await mkdtemp(join(tmpdir(), "pitlane-ipc-")); + const directory = await mkdtemp(join(tmpdir(), "simlock-ipc-")); const endpoint = join(directory, "daemon.sock"); const ipc = new NodeIpcTransport(); try { diff --git a/src/ports/logger.test.ts b/src/ports/logger.test.ts index 95cd347..1bcf789 100644 --- a/src/ports/logger.test.ts +++ b/src/ports/logger.test.ts @@ -125,7 +125,7 @@ afterEach(async () => { }); async function tempDir(): Promise { - const directory = await mkdtemp(join(tmpdir(), "pitlane-logger-")); + const directory = await mkdtemp(join(tmpdir(), "simlock-logger-")); temporaryDirectories.push(directory); return directory; } diff --git a/src/ports/paths.test.ts b/src/ports/paths.test.ts index fb46ba4..0e8d608 100644 --- a/src/ports/paths.test.ts +++ b/src/ports/paths.test.ts @@ -2,14 +2,14 @@ import { homedir } from "node:os"; import { join } from "node:path"; import { describe, expect, it } from "vitest"; -import { resolvePitlaneHome } from "./paths.js"; +import { resolveSimlockHome } from "./paths.js"; -describe("resolvePitlaneHome", () => { - it("defaults to ~/.pitlane when PITLANE_HOME is unset", () => { - expect(resolvePitlaneHome({})).toBe(join(homedir(), ".pitlane")); +describe("resolveSimlockHome", () => { + it("defaults to ~/.simlock when SIMLOCK_HOME is unset", () => { + expect(resolveSimlockHome({})).toBe(join(homedir(), ".simlock")); }); - it("uses PITLANE_HOME when set", () => { - expect(resolvePitlaneHome({ PITLANE_HOME: "/tmp/custom-home" })).toBe("/tmp/custom-home"); + it("uses SIMLOCK_HOME when set", () => { + expect(resolveSimlockHome({ SIMLOCK_HOME: "/tmp/custom-home" })).toBe("/tmp/custom-home"); }); }); diff --git a/src/ports/paths.ts b/src/ports/paths.ts index ef13b65..1a12cbd 100644 --- a/src/ports/paths.ts +++ b/src/ports/paths.ts @@ -2,11 +2,11 @@ import { homedir } from "node:os"; import { join } from "node:path"; /** - * Resolves pitlane's data directory (config.json, state.json, daemon.sock, daemon.log). - * `PITLANE_HOME` overrides the default `~/.pitlane` -- used by tests that need an + * Resolves simlock's data directory (config.json, state.json, daemon.sock, daemon.log). + * `SIMLOCK_HOME` overrides the default `~/.simlock` -- used by tests that need an * isolated data directory per daemon instance, and by anyone running multiple - * independent pitlane installs on one machine. + * independent simlock installs on one machine. */ -export function resolvePitlaneHome(env: NodeJS.ProcessEnv = process.env): string { - return env.PITLANE_HOME ?? join(homedir(), ".pitlane"); +export function resolveSimlockHome(env: NodeJS.ProcessEnv = process.env): string { + return env.SIMLOCK_HOME ?? join(homedir(), ".simlock"); } diff --git a/src/ports/process-runner.test.ts b/src/ports/process-runner.test.ts index 36caf84..f6ef90a 100644 --- a/src/ports/process-runner.test.ts +++ b/src/ports/process-runner.test.ts @@ -42,10 +42,10 @@ describe("ScriptedProcessRunner", () => { const runner = new ScriptedProcessRunner([ { hangs: true, - match: { command: "emulator", args: ["@pitlane"] }, + match: { command: "emulator", args: ["@simlock"] }, }, ]); - const process = runner.spawn("emulator", ["@pitlane"]); + const process = runner.spawn("emulator", ["@simlock"]); const result = process.wait(); process.kill("SIGTERM");