/** Single source of truth for the slice unit name. Used by the spawn argv,
 *  the boot-seed list-units filter, and the installer's slice unit
 *  emission. */
export declare const CLAUDE_PTYS_SLICE = "claude-ptys.slice";
/** Hard cap on SIGTERM→SIGKILL escalation inside the scope. systemd
 *  delivers SIGTERM on stop, waits TimeoutStopSec, then SIGKILL. 5 s
 *  matches the manager's prior killGraceMs (`config.killGraceMs`); the
 *  manager no longer manages the escalation itself. */
export declare const SCOPE_TIMEOUT_STOP_SEC = 5;
/** Result of one systemctl invocation. Mirrors the shape of
 *  spawnSync('utf8') return — exit code plus captured streams. The
 *  scope-stop and list-units paths both go through this shape so tests
 *  can inject a single stub. */
export interface SystemctlResult {
    exitCode: number;
    stdout: string;
    stderr: string;
}
/** Synchronous runner for `systemctl <args...>`. Default impl uses
 *  spawnSync (matches uninstall.ts's existing idiom and keeps the call
 *  site free of async glue for what is in practice a sub-50ms call).
 *  Tests inject a recording stub. */
export type SystemctlRunner = (args: readonly string[]) => SystemctlResult;
export declare function defaultSystemctlRunner(args: readonly string[]): SystemctlResult;
export declare function scopeUnitName(unitToken: string): string;
/** Inverse of `scopeUnitName` — extracts the unitToken from a unit name
 *  emitted by `systemctl --user list-units --type=scope 'claude-session-*.scope'`.
 *  Returns null when the name doesn't match the prefix/suffix shape. */
export declare function unitTokenFromScopeUnit(unit: string): string | null;
/** One row from `systemctl --user list-units --type=scope --no-legend
 *  'claude-session-*.scope'`. The `loaded`/`active`/`sub` columns let the
 *  boot-seed reconciler ignore units that are draining (`sub=stop-sigterm`)
 *  vs running (`sub=running`). */
export interface SurvivingScope {
    unitName: string;
    unitToken: string;
    loaded: string;
    active: string;
    sub: string;
}
/** Parse the `--no-legend` output of `systemctl --user list-units`.
 *  Tolerant of leading column whitespace and trailing description column.
 *  Rows whose unit name doesn't extract a unitToken are skipped (defensive
 *  against future systemctl-output drift). */
export declare function parseSurvivingScopes(stdout: string): SurvivingScope[];
/** Issue `systemctl --user list-units --type=scope --no-legend
 *  'claude-session-*.scope'` and return the parsed rows. The boot-seed
 *  uses this to discover surviving PTYs across a manager restart. */
export declare function listSurvivingScopes(run: SystemctlRunner): {
    scopes: SurvivingScope[];
    result: SystemctlResult;
};
/** Stop any orphan rc scope units left over from prior session-manager
 *  boots. Called from the session-manager startup BEFORE the rc-daemon
 *  supervisor's first spawn and BEFORE the HTTP server begins accepting
 *  /rc-spawn requests, so no in-flight scope can be misidentified as an
 *  orphan. Idempotent and safe to call on every boot — if nothing
 *  matches, no stop is issued. */
export declare function reapOrphanNonPtyScopes(run: SystemctlRunner, logger: (line: string) => void): Promise<void>;
/** Outcome surface for a scope-stop attempt.
 *
 *  Task 451 — `outcome=ok` means **unit stopped AND pid is gone**. Pre-451,
 *  `ok` was emitted whenever `systemctl stop` exited 0, even when the unit
 *  was already collapsed and the underlying claude pid had been reparented
 *  outside the scope cgroup (still running). The added post-flight
 *  `kill(pid, 0)` check distinguishes that case:
 *
 *    - `ok`               — stop returned 0 AND (pid was not supplied OR pid is gone)
 *    - `already-gone`     — stop returned exit code 5 (unit not loaded)
 *    - `escalated-term`   — pid survived `systemctl stop`; SIGTERM cleared it within grace
 *    - `escalated-kill`   — SIGTERM didn't suffice; SIGKILL cleared it
 *    - `failed-pid-survives` — pid is still alive after SIGKILL (rare; signal-blocked
 *      process, or a zombie whose parent has not reaped). Caller surfaces as a
 *      distinct error class.
 *    - `error`            — systemctl exit was non-zero and non-5 (a real systemctl
 *      failure unrelated to unit state)
 */
export type StopScopeOutcome = 'ok' | 'already-gone' | 'escalated-term' | 'escalated-kill' | 'failed-pid-survives' | 'error';
export interface StopScopeResult {
    outcome: StopScopeOutcome;
    exitCode: number;
    stderr: string;
}
/** Injectable killer + alive-check for tests; production wires
 *  `process.kill`. `kill(pid, 0)` returns whether the pid is currently
 *  reachable (alive AND signal-permitted by the caller's uid). */
export interface ProcessSignals {
    /** Send `signal` to `pid`. Throws on ESRCH (no such pid) or EPERM. */
    kill(pid: number, signal: number | string): void;
    /** True when `pid` is alive and the caller has permission to signal it.
     *  ESRCH → false. EPERM → true (alive but not ours; treat as alive so
     *  we never mistakenly call the pid dead). Any other throw → true,
     *  on the same "fail safe, escalate" principle. */
    isAlive(pid: number): boolean;
}
export declare const defaultProcessSignals: ProcessSignals;
/** Issue `systemctl --user stop --no-block <unit>` and verify the kill.
 *
 *  Task 451 — when `pid` is supplied, after `systemctl stop` returns we
 *  check `kill(pid, 0)`. If the pid is still alive (the scope was already
 *  collapsed before stop ran, so the pid was reparented outside the cgroup
 *  and stop's `exit=0` was a lie), escalate: SIGTERM → wait `killGraceMs`
 *  → re-check → SIGKILL → wait → re-check. Outcome reflects the path
 *  taken. When `pid` is null (caller cannot supply, e.g. genuine orphan
 *  with no MainPID), the function behaves as pre-451 — exit code is the
 *  only signal.
 *
 *  The --no-block flag returns once the stop request is queued; the
 *  scope's TimeoutStopSec=5 handles in-cgroup SIGTERM→SIGKILL escalation.
 *  Exit code 5 ("not loaded") still means the scope retired naturally —
 *  treated as `already-gone`. */
export declare function stopScopeUnit(run: SystemctlRunner, unitToken: string, opts?: {
    pid?: number | null;
    killGraceMs?: number;
    signals?: ProcessSignals;
}): Promise<StopScopeResult>;
/** Argv shape produced by `buildSystemdRunArgv`. The cmd is always
 *  `systemd-run`; the manager passes this to node-pty. */
export interface SystemdRunArgv {
    cmd: 'systemd-run';
    args: string[];
}
export interface BuildSystemdRunArgvParams {
    unitToken: string;
    claudeBin: string;
    claudeArgs: readonly string[];
    /** Full env map to thread into the scope via `--setenv=KEY=VAL`. Every
     *  pair lands as one flag. The default systemd-run inherit-from-caller
     *  is NOT relied on; spawnPtyAdapter composes the explicit env block
     *  (per-spawn overrides + a copy of process.env it wants in the scope)
     *  and the helper threads exactly that shape. */
    env: Readonly<Record<string, string>>;
    /** Working directory passed via `--working-directory=`. The manager's
     *  prior node-pty spawn passed `cwd`; the scope receives it the same way. */
    cwd: string;
}
/** Build the systemd-run argv that node-pty wraps for one claude spawn.
 *  Inside the scope, `/bin/sh -c 'trap "" HUP; exec "$@"' sh <claudeBin> <claudeArgs...>`
 *  wraps the launch so:
 *
 *  - **claude ignores SIGHUP** when the manager closes the PTY master fd
 *    on restart. Without the trap, master-close → kernel SIGHUP →
 *    claude exits.
 *  - **`exec` keeps the pid stable** through the chain (sh → claude
 *    reuse the original pid via execve), preserving the
 *    `pty.pid == claude.pid` invariant the watcher.waitForPid path
 *    depends on.
 *
 *  Task 573 — node-pty allocates the TTY for the child. The earlier
 *  script(1) wrap (Task 556) plus the non-PTY scope primitive it served
 *  are gone. */
export declare function buildSystemdRunArgv(p: BuildSystemdRunArgvParams): SystemdRunArgv;
/** Query `systemctl --user show -p MainPID claude-session-<token>.scope`
 *  and return the main pid (the claude process). Boot-seed uses this to
 *  bridge from the surviving scope unit to the PID file claude wrote at
 *  `$CLAUDE_CONFIG_DIR/sessions/<pid>.json`, which in turn carries claude's
 *  intrinsic sessionId. Returns null when the unit has no main pid (the
 *  scope is draining) or when the systemctl call fails. */
export declare function getScopeMainPid(run: SystemctlRunner, unitToken: string): number | null;
/** Slice unit declared at install time so every claude scope shares one
 *  cohort cgroup. `systemctl --user status claude-ptys.slice` gives the
 *  operator a one-glance view of total live-session memory + CPU. */
export declare function buildClaudePtysSliceUnitFile(): string;
//# sourceMappingURL=systemd-scope.d.ts.map