/** * The wicked-interactive bridge pool — discovery, health, reuse-or-start (DES-MERGE-001 §5.3/§5.6). * * A bridge is a local `wicked-interactive serve` process that records itself in * `/.wi-serve.json` = `{ port, host, pid, startedAt, version }` (ADR-0022). Its port is * DYNAMIC — the first free port above its base — which is precisely why crew, a server process * that can read that lockfile, proxies it instead of the browser dialling a port literal. * * LOCAL-ONLY BY DESIGN. Every mechanism here (a pid, a file in a directory, spawning a child) * is single-host. That is the slice-1 posture, not an oversight: when the execution seam goes * remote, this module is the seam that gets a remote implementation, and the proxy above it * does not change. * * Pooling is keyed by the RESOLVED root (see `bridge-root.ts`), so two projects that share the * default root share one bridge and one port; projects bound to different roots get their own. * * Health follows ADR-0025's hardened reuse check, and the ORDER matters: a recorded pid that is * alive but slow (cold first hit, busy materializing) must be REUSED, not duplicated — so a * live pid earns three 1.5 s attempts before the bridge is declared dead. Identity is checked * too: `/api/health` must report THIS root, or a recycled port belonging to some other service * would be proxied as if it were ours. * * On start/adopt the pool also records the daemon's own origin with the bridge (crew#298, * `POST /api/studio-origin`, interactive ≥ 0.8.0) so the bridge's `GET /` redirects a direct * visitor into studio instead of its API-only fallback page. Fire-and-forget, once per pooled * bridge: recording can never fail — or slow down — a proxied request. * * THE SPAWN ENV (acceptance findings F-042 + F-043). The bridge validates and registers a doc's * project against `WICKED_CREW_API` — until interactive 0.9.3 defaulting to `http://127.0.0.1:7701` * when unset; since 0.9.3 it FAILS CLOSED instead (R-L7-a: binding, picker and the runs probe are * off and name the variable) — and emits onto the wicked-bus at `WICKED_BUS_DATA_DIR`, defaulting * to `~/.something-wicked/wicked-bus`. A bridge spawned with the daemon's bare env therefore talked * to whatever daemon owned :7701 (a * daemon on any other port could not create a single project-bound document) and shared ONE bus * with every other daemon on the host (two daemons' durable cursors racing for one `doc.created`). * So the spawn now exports BOTH: `WICKED_CREW_API` = this daemon's own bound origin, and * `WICKED_BUS_DATA_DIR` = the directory of the bus this daemon's interactive seams read. The pair * is written beside the lockfile (`.wi-serve.crew.json`) so an ADOPTED bridge can be checked: one * this daemon (or a sibling) started with a DIFFERENT pair is recycled — restarted with the right * env — and one nobody recorded (an operator-run `wicked-interactive serve`, a pre-upgrade bridge) * is adopted with a warning that names the fix. A bridge is only useful to the daemon whose bus it * emits to; sharing one across daemons is exactly the isolation break F-043 recorded. * * RECYCLING FAILS CLOSED (codex r4 on crew#506). Stopping a mismatched bridge is only "done" when * its pid is DEMONSTRABLY gone: a refused signal (EPERM — a bridge running as another user) or a * pid that survives SIGTERM, the grace and SIGKILL refuses the start, naming the pid and the daemon * that owned it, and leaves the sidecar untouched — a replacement is never started beside a bridge * that may still be running. The replacement itself must prove it is OURS before it is recorded as * crew's: the lockfile pid has to be the child this daemon spawned or one of its descendants * (`npx` → shell → node), it must differ from the pid just recycled, and a bridge whose lineage * cannot be read is used but never written into the sidecar. A sidecar entry is a claim of * ownership that licenses a later kill, so it is only ever written for a pid this daemon spawned. * * LIFETIME (F-W1-103, FIX-IT-ALL wave 1): a crew-spawned bridge lives exactly as long as a crew * daemon OWNS it — adopt-or-kill, never leak. The sidecar's `ownerPid` + `ownerStartedAt` is that * daemon — one process incarnation, so a recycled pid never reads as a live owner — the spawner, or * the daemon that last adopted the bridge (adoption stamps the adopter). The daemon's shutdown * reaps the bridges it spawned (the npm wrapper AND its server child), and every daemon's boot and * periodic sweeps reap a bridge tree reparented to init whose sidecar owner is gone * (`core/bridge-reaper.ts`). Two bridges on ports nothing would ever look up again, ~20 h after * their daemon exited, were the finding. */ import { type ChildProcess } from 'node:child_process'; import type { AddressInfo } from 'node:net'; export declare const LOCK_NAME = ".wi-serve.json"; /** Crew's sidecar beside the bridge's lockfile: which pid crew started, and with which env (F-042/F-043). */ export declare const CREW_SIDECAR_NAME = ".wi-serve.crew.json"; /** How long a recycled bridge gets to exit on SIGTERM before SIGKILL. */ export declare const RECYCLE_GRACE_MS = 3000; /** How long after SIGKILL the pid gets to leave the process table before the recycle is refused. */ export declare const RECYCLE_HARD_MS = 1000; /** Parent hops walked when proving a lockfile pid descends from the child this daemon spawned. */ export declare const LINEAGE_MAX_HOPS = 16; /** The range crew needs when nothing overrides it (see F-081 above). */ /** * 0.10.0 (EP-I1 + EP-I2, R-I): CREW may emit `review.completed`, and `PUT /d/:doc/api/theme/learned` * exists — the route crew's theme seam (EP-C4) writes through. A 0.9.x bridge has no such route, so * a validated theme would fail at the bridge (codex r1 on EP-C4): the floor follows. */ /** * 0.11.1 (wicked-interactive#272, crew#509): `serve` echoes the crew-issued * `WICKED_BRIDGE_SPAWN_TOKEN` into `.wi-serve.json` as `spawnToken`, so {@link tokenLineage} proves * ownership without the process table; below it the pool takes the ps-walk fallback (#882). */ /** * 0.12.0 (codex audit EXP-03, wicked-interactive#287): export asset reads are confined, by realpath, * to the document's root and `WI_EXPORT_ASSET_ROOTS`, so a relative, absolute or symlinked reference * can no longer inline a host file into an export. Below it a bridge reads whatever path a document * names: the floor follows the security fix. */ /** * 0.14.1: a demo run's import (`PUT /d/:doc/api/demo/import/:chapter` + `POST …/demo/import`, which * `api/recording.ts` calls) shipped in 0.13.0, and studio's "Learn from a brand" upload * (`PUT /d/:doc/api/theme/source`) in 0.14.0, which was tagged but never published; 0.14.1 is the first * build with both. A caret on 0.x pins the minor, so `^0.12.0` spawned 0.12.x, which has neither route. */ export declare const INTERACTIVE_DEFAULT_RANGE = "^0.14.1"; /** The env override of the RANGE (not the package). */ export declare const INTERACTIVE_SPEC_ENV = "WICKED_INTERACTIVE_SPEC"; /** The default spec — what the daemon spawns with no override. */ export declare const INTERACTIVE_SPEC = "wicked-interactive@^0.14.1"; /** A semver RANGE this daemon will resolve: one comparator, or a space-separated intersection of * them (`>=0.9.1 <1.0.0`). Tags (`latest`), `x`-ranges, `||` unions, paths and `pkg@range` spellings * are refused — the override names a floor within the package, nothing else. */ export declare function validInteractiveRange(raw: string): boolean; export interface InteractiveSpecResolution { /** The npm spec to spawn: `wicked-interactive@`. */ spec: string; range: string; source: 'default' | 'env'; /** Present when the env variable was set but is not a semver range — ignored, default used. */ rejected?: string; /** Present when an ACCEPTED override's floor is below crew's default floor (#533 review, F-7): the * override is honoured — a pin-back is the operator's call — but routes crew calls may be missing * (0.8.x lacks `DELETE /api/docs/:doc`), so the boot line warns. */ belowFloor?: true; } /** `MAJOR.MINOR.PATCH` of the first floor-bearing comparator in a range (`^`, `~`, `>=`, `=`, bare); * `null` when the range has only upper bounds. */ export declare function rangeFloor(range: string): [number, number, number] | null; /** Resolve the spec from `env` — pure, so the boot line, the spawn and the hint agree. */ export declare function resolveInteractiveSpec(env?: NodeJS.ProcessEnv): InteractiveSpecResolution; /** The spec the daemon spawns and the hint names — resolved from the live env at each use. */ export declare function interactiveSpec(env?: NodeJS.ProcessEnv): string; /** The installed bridge binary crew prefers over npx (crew#499). */ export interface InteractiveBinary { /** The PATH entry (what a shell would run), not its symlink target. */ path: string; /** The installed package's `version`. */ version: string; } /** * crew#499 (F-044, F-W4-110): the FIRST `wicked-interactive` on `PATH` — what a shell would run — * when its installed package version satisfies the spec's range (the default or the operator's * `WICKED_INTERACTIVE_SPEC`), else `null` and the spawn falls back to npx. The version is read from * the package's own `package.json` beside the binary's real path (an npm global install links * `/bin/` into `/lib/node_modules//…`); nothing is executed. A binary * whose package cannot be found, or a platform whose shims are not links (Windows `.cmd`), falls * back to npx too. */ export declare function resolveInteractiveBinary(env?: NodeJS.ProcessEnv): InteractiveBinary | null; /** ADR-0025: 1.5 s × 3 while the pid lives. */ export declare const HEALTH_TIMEOUT_MS = 1500; export declare const HEALTH_ATTEMPTS = 3; /** A cold `npx wicked-interactive serve` may have to resolve and fetch the package first. */ export declare const START_TIMEOUT_MS = 60000; /** A bridge that answered `/api/health` for its root. */ export interface LiveBridge { host: string; port: number; pid: number; /** The lockfile's own `startedAt` (ADR-0022), when it carries one: the bridge INSTANCE marker a * sidecar is checked against, so a reused pid is never mistaken for the bridge crew recorded * (crew#510). Null when the lockfile predates the field or does not carry it. */ startedAt?: string | null; /** crew#509: the `WICKED_BRIDGE_SPAWN_TOKEN` the bridge was started with, as its lockfile echoes it * (`spawnToken`). Null when the lockfile does not carry one (an interactive before the echo, or a * bridge started without a token — an operator's own `serve`). */ spawnToken?: string | null; } /** crew#509: the per-spawn token crew hands a bridge it starts; a token-aware bridge writes it back * into its lockfile as `spawnToken`, which proves the bridge is the one THIS spawn started — no * process-table walk. NOT part of {@link BridgeEnv}: it differs per spawn by design, and the env * match is about configuration. */ export declare const SPAWN_TOKEN_ENV = "WICKED_BRIDGE_SPAWN_TOKEN"; /** The 503 the proxy renders as `{"code":"bridge_unavailable","hint":...}` (§5.6). */ export declare class BridgeUnavailableError extends Error { /** An ACTIONABLE command an operator can actually run — never a bare "try again". */ readonly hint: string; constructor(message: string, hint: string); } /** The three variables crew hands the bridge it spawns (F-042/F-043, BC-50). Absent = not set — * the bridge falls back to its own defaults, which is exactly the pre-fix behavior this exists to * end (since interactive 0.9.3 an unset `WICKED_CREW_API` fails CLOSED there — R-L7-a). */ export interface BridgeEnv { /** The daemon's own origin — where the bridge validates/registers project bindings. */ WICKED_CREW_API?: string; /** The directory holding the `bus.db` this daemon's interactive seams read. */ WICKED_BUS_DATA_DIR?: string; /** Where the recorder's Playwright browser is provisioned AND looked for: * `/interactive/recorder-browsers` (BC-50 / R-L7-d), never the global cache. */ PLAYWRIGHT_BROWSERS_PATH?: string; } /** What crew writes beside the lockfile after IT starts a bridge. */ export interface CrewSidecar { /** The bridge's pid (the lockfile's). */ pid: number; env: BridgeEnv; startedBy: 'wicked-crew'; startedAt: string; /** The DAEMON that owns it: the spawner, or the daemon that last ADOPTED it (F-W1-103 — adoption * stamps the adopter, so the orphan sweeps see a bridge in use). A bridge whose owner is still * alive is that daemon's to stop — never this one's (codex on crew#506). */ ownerPid?: number; /** The owner's process start time as the process table reports it ({@link processStartedAt}), so * `ownerPid` names ONE incarnation of that pid: a recycled pid reads as "owner gone" instead of * keeping the bridge alive for as long as the pid stays taken (crew #606 review, MED-2). Absent * on a sidecar written before this field existed — pid liveness is all those can be checked by. */ ownerStartedAt?: string; /** The BRIDGE instance this sidecar is about: the lockfile's own `startedAt` at the moment crew * recorded it (crew#510). A pid alone does not identify a process for longer than that pid * stays taken, so `pid` + this marker is what licenses an adopt or a recycle; a live lockfile * that does not carry the same marker is a DIFFERENT bridge that merely inherited the pid. * Absent on a sidecar written before this field existed (checked the legacy way — see * {@link sidecarNamesBridge} — and upgraded on the next adopt). */ bridgeStartedAt?: string; /** crew#509: the spawn token this bridge echoed when crew recorded it. With the lockfile's own * `spawnToken` it names ONE bridge instance regardless of pid reuse. Absent on a sidecar written * before the field, or for a bridge that does not echo the token. */ spawnToken?: string; } /** Injectable IO — the integration suite substitutes a fake bridge for the real `npx` spawn. */ export interface BridgePoolIo { /** The spawn; the second argument is the FULL child env (the daemon's, plus {@link BridgeEnv}). */ spawn?: (root: string, env: NodeJS.ProcessEnv) => ChildProcess; /** Signal delivery for a recycle (tests inject a refusal). Default `process.kill`. */ kill?: (pid: number, signal: NodeJS.Signals) => void; /** Liveness probe while waiting for a recycled bridge to leave. Default {@link pidAlive}. */ alive?: (pid: number) => boolean; /** Parent-pid lookup for the spawn-lineage proof (tests inject). Default {@link parentPidOf}. */ parentOf?: (pid: number) => number | null; /** Recycle timing overrides (tests); defaults {@link RECYCLE_GRACE_MS} / {@link RECYCLE_HARD_MS}. */ recycleGraceMs?: number; recycleHardMs?: number; startTimeoutMs?: number; healthTimeoutMs?: number; log?: (msg: string) => void; /** Lower-severity channel for EXPECTED skips (a pre-0.8.0 bridge without the endpoint, #298). */ debug?: (msg: string) => void; /** * The daemon's own origin (`http://:`), resolved LAZILY — the pool is * built before `listen`, but only consulted while serving a request, i.e. after the address is * bound. Null (or absent) means "nothing to record" and the pool never POSTs (#298). It is also * the `WICKED_CREW_API` the spawned bridge gets (F-042) — the bridge must talk to THIS daemon. */ studioOrigin?: () => string | null; /** * The directory of the bus db this daemon's interactive seams read — exported to the spawned * bridge as `WICKED_BUS_DATA_DIR` so both meet on one bus (F-043), and recorded in the sidecar so * an adopted bridge can be checked against it. Null/absent = not exported (the bridge keeps * wicked-bus's own default): the CLI passes null only when `--bus-db` names a file wicked-bus * cannot be pointed at through a data dir, or wicked-bus is not importable at all. */ busDataDir?: string | null; } /** The {@link BridgeEnv} this pool hands a bridge it starts, from its io. Only DEFINED values ride; * the browsers path always does — it is crew's placement, under the daemon's own state home. */ export declare function bridgeEnvFor(io: Pick): BridgeEnv; export { busDataDirOf } from './bus-location.js'; /** `true` when two bridge envs agree on every variable either one sets. A sidecar written before * the browsers-path key existed (crew ≤ 0.7.34) does NOT match — that mismatch is the recycle * that carries a live bridge onto interactive 0.9.3 (see the 0.9.3 note above). */ export declare function bridgeEnvMatches(a: BridgeEnv, b: BridgeEnv): boolean; /** `/.wi-serve.crew.json`, or null when absent/unparseable/incomplete. */ export declare function readCrewSidecar(root: string): CrewSidecar | null; /** * Clock slack on the legacy (pre-{@link CrewSidecar.bridgeStartedAt}) instance check, in ms. * * Crew writes the sidecar only after the bridge answered `/api/health`, so the bridge's own * lockfile `startedAt` always PRECEDES the sidecar's `startedAt` — by the health round trip. The * two timestamps are written by two processes, so a millisecond of non-monotonicity is possible; * one second absorbs that without weakening the check, because the stale sidecar this catches * names a bridge that started after crew recorded a DIFFERENT one on that pid. */ export declare const SIDECAR_LOCK_SKEW_MS = 1000; /** What a sidecar says about the bridge whose lockfile is live right now. */ export type SidecarMatch = /** This sidecar is about THIS bridge instance: it may be adopted, claimed, or recycled. */ 'names-it' /** It names this pid, but the live bridge is a different INSTANCE — the recorded bridge died and * its pid was reused. Never an adopt-silently and never a kill licence (crew#510). */ | 'stale' /** It is about some other pid entirely. */ | 'other-pid'; /** * Is `sidecar` about the bridge `live`'s lockfile names (crew#510)? * * A pid identifies a process only for as long as that pid stays taken. `writeSidecar` records the * bridge's lockfile `startedAt` beside the pid, so the pair names ONE bridge instance: a lockfile * whose marker differs was written by a different bridge that inherited the pid — an operator's * `wicked-interactive serve`, another daemon's pool — and crew must neither adopt it as its own * nor recycle (SIGTERM/SIGKILL) it, which is the "never kill a process crew did not start" rule. * * A sidecar written before the marker existed is checked the only way it can be: the bridge's * lockfile cannot have been written AFTER crew recorded the sidecar (crew records it once the * bridge is already answering), so a lockfile that started later is a different bridge. When * neither side carries a timestamp there is nothing to check and the pid stands alone — today's * behaviour, and the next adopt upgrades the sidecar with the marker. */ export declare function sidecarNamesBridge(sidecar: CrewSidecar, live: LiveBridge): SidecarMatch; /** * The daemon's own http origin from its bound server address, or null before `listen`. Wildcard * binds (`0.0.0.0` / `::`) are not dialable from a browser, so they normalize to loopback — the * bridge is local-only anyway, so whoever hits its port can reach the daemon at 127.0.0.1 too. */ export declare function boundOrigin(addr: AddressInfo | string | null): string | null; /** `/.wi-serve.json`, or null when absent/unparseable/incomplete. */ export declare function readLock(root: string): LiveBridge | null; /** Signal 0 probes existence without delivering: EPERM means alive-but-not-ours. */ export declare function pidAlive(pid: number): boolean; /** * The start time of `pid` as the process table reports it, or null when it cannot be read. Together * with the pid it identifies ONE process incarnation — a recycled pid has a different start — which * is what a sidecar's `ownerPid` needs to mean "that daemon" on a host that stays up for weeks. * POSIX asks `ps -o lstart=` (the same spelling on macOS and procps); Windows asks CIM. Read once * per bridge start / adoption and once per orphan candidate — never on a hot path. */ export declare function processStartedAt(pid: number): string | null; /** This daemon's own start time, read once — what it writes into every sidecar it owns. */ export declare function daemonStartedAt(): string | null; /** * Is the daemon a sidecar names as owner still THAT daemon? Alive means the pid is in the table AND, * when the sidecar recorded the owner's start time, the pid's start time today is the same one — a * recycled pid is "owner gone" (crew #606 review, MED-2). A sidecar without `ownerStartedAt` can only * be checked by pid (disclosed gap); an UNREADABLE start time today reads as alive — a possibly-live * daemon's bridge is never treated as reapable on uncertainty. No owner at all is "not alive". * `io` is for tests (a fake table); production uses the real one. */ export declare function ownerAlive(sidecar: Pick, io?: { alive?: (pid: number) => boolean; startedAt?: (pid: number) => string | null; }): boolean; /** * The parent pid of `pid`, or null when it cannot be read (no such process, no `ps` / PowerShell, * an unparseable answer). POSIX asks `ps`; Windows asks CIM. Only consulted once per bridge start. */ export declare function parentPidOf(pid: number): number | null; /** What the lineage proof concluded about a lockfile pid relative to the child this daemon spawned. */ export type SpawnLineage = 'ours' | 'foreign' | 'unknown'; /** * crew#509: the ownership proof through the bridge itself. A lockfile that echoes the token THIS * spawn issued is ours; one that echoes a DIFFERENT token is another daemon's spawn (foreign); one * that carries none (an interactive before the echo, or an operator's own `serve`) is undecided * here — `null`, and the caller falls back to {@link spawnLineage}'s process-table walk. */ export declare function tokenLineage(live: Pick, issued: string): SpawnLineage | null; /** * Is `pid` the child this daemon spawned (`childPid`) or one of its descendants (`npx` → shell → * node)? Walks parents until it meets `childPid` (ours), reaches the root of the process tree or a * cycle (foreign — somebody else's bridge took the lockfile), or cannot read a hop / runs out of * hops (unknown — used but never recorded as crew's). Pure: the lookup is injected. */ export declare function spawnLineage(pid: number, childPid: number, parentOf: (pid: number) => number | null): SpawnLineage; export declare class InteractiveBridgePool { private readonly io; /** Last bridge known good per root — the fast path that keeps the proxy off `fetch` per request. */ private readonly live; /** In-flight resolutions per root, so a burst of first requests starts ONE bridge, not N. */ private readonly inflight; constructor(io?: BridgePoolIo); /** Reuse-or-start, idempotent per root. Throws `BridgeUnavailableError` when start is impossible. */ ensure(root: string): Promise; /** Drop the cached bridge for a root — called when a proxied connection is refused. */ invalidate(root: string): void; /** Live bridges, for tests and future operator introspection. */ keys(): string[]; private resolveOrStart; /** * A live bridge was found through the lockfile. Is it one THIS daemon can use (F-042/F-043)? * - crew's sidecar names this pid and its env matches ours → adopt silently, and stamp THIS * daemon as its owner (F-W1-103) so the reaper's orphan sweeps — which reap a crew bridge whose * owner daemon is gone — see it in use for as long as this daemon lives; * - the sidecar names this pid with a DIFFERENT env: if the daemon that owns it (`ownerPid`, the * SAME incarnation — pid and start time, {@link ownerAlive}) is STILL ALIVE, the bridge is that daemon's — two daemons sharing one docs root — and this * one must not kill it mid-create (codex on crew#506): refuse with a `BridgeUnavailableError` * naming the owner and the fix (give this daemon its own interactive root). Only a bridge * whose owner is gone (a previous daemon that exited, or this very process after a * reconfiguration) is recycled — SIGTERM, grace, SIGKILL — and restarted with the right env; * - no sidecar (or another pid) → nobody recorded how it was started (an operator's terminal * `wicked-interactive serve`, a pre-upgrade bridge): adopt it, but say what it may be missing * and how to fix it. Killing a process crew did not start is not crew's call. */ private adoptOrRecycle; /** A bridge nobody recorded (an operator's terminal `wicked-interactive serve`, a pre-upgrade * bridge, or one whose record turned out to be about a dead predecessor on the same pid — * crew#510): adopted as-is, with a warning naming what it may be missing and the fix. Never * claimed as crew's and never killed. */ private adoptUnrecorded; /** * SIGTERM, wait up to {@link RECYCLE_GRACE_MS} for exit, SIGKILL whatever ignored it — and then * PROVE the pid is gone. Fails closed (codex r4 on crew#506): a refused signal (EPERM) or a pid * still in the process table after the hard wait throws a `BridgeUnavailableError` naming the pid * and its owner, so no replacement is started beside a bridge that may still be running and the * sidecar that records it is never touched. ESRCH is not a failure — the bridge left before we * signalled — but even then the pid has to be observed gone. * * Once the exit IS proven, the sidecar is dropped (crew#510): it records a pid that no longer * exists, and a record left behind is exactly what a later pid reuse reads as crew's own bridge. * The replacement writes a fresh one; a start that then fails leaves no claim on that pid at all. */ private terminate; /** * POST the daemon's own origin to the bridge's `/api/studio-origin` (#298), so the bridge's * `GET /` redirects a direct visitor into studio instead of the API-only fallback page. * At most one attempt per pooled bridge per process; 404/405 means the bridge predates the * endpoint (interactive < 0.8.0) and is an expected skip, any other failure is a warn — never * an error, because origin recording must never fail a proxy request. */ private recordStudioOrigin; /** The lockfile points at a bridge that is alive, answering, and serving THIS root. */ private healthy; /** * Spawn a bridge for `root` and wait for it to answer. `replacing` is the pid just recycled * (see {@link adoptOrRecycle}). Before the new bridge is used — and before ANY sidecar is * written — it has to prove it is ours: not the recycled pid (that bridge was not stopped), and * the child this daemon spawned or one of its descendants (`npx` → shell → node, checked through * {@link spawnLineage}). A bridge somebody else started under our lockfile while we were * starting is refused (and our own spawn abandoned); one whose lineage cannot be read is used * but NOT recorded — a sidecar is a claim of ownership, and only a pid this daemon spawned earns it. */ private start; /** Adopting a crew-started bridge makes THIS daemon its owner (F-W1-103): rewrite the sidecar's * `ownerPid` — pid, env and `startedAt` untouched — so the orphan sweeps, which reap a crew * bridge whose owner daemon is gone, see it in use. A pre-crew#510 record is also stamped with * the live bridge's instance marker here, which is the one moment that marker is provable * without having spawned the bridge: the sidecar was just judged to be about THIS instance. * Best-effort, like {@link writeSidecar}. */ private claimSidecar; /** Drop a sidecar that provably describes a bridge that is GONE (crew#510): the pid it names has * been observed to belong to another process, or has been proven to have exited. Leaving it * would let a later pid reuse read as crew's own bridge. Best-effort: the adopt-time checks * above already refuse to trust it, so a file that cannot be removed costs a repeated warning, * never a wrong decision. */ private discardSidecar; /** Record which pid crew started and with which env, so a later adopt can tell ours from a * sibling daemon's (see {@link adoptOrRecycle}). Only ever called for a pid whose lineage from * this daemon's own spawn is PROVEN (see {@link start}). Best-effort: an unwritable sidecar only * costs the adopt-time check, never the start. */ private writeSidecar; } //# sourceMappingURL=bridge-pool.d.ts.map