@crouter/api 0.3.387 → 0.3.388

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/dist/api/__tests__/integration/client.test.js +97 -0
  2. package/dist/api/client.d.ts +7 -0
  3. package/dist/api/client.js +40 -21
  4. package/dist/core/asset-root.d.ts +7 -0
  5. package/dist/core/asset-root.js +18 -0
  6. package/dist/core/canvas/boot-id.d.ts +6 -0
  7. package/dist/core/canvas/boot-id.js +26 -0
  8. package/dist/core/canvas/paths.d.ts +72 -0
  9. package/dist/core/canvas/paths.js +163 -0
  10. package/dist/core/canvas/pid.d.ts +391 -0
  11. package/dist/core/canvas/pid.js +948 -0
  12. package/dist/core/command-plugins/bundle.d.ts +149 -0
  13. package/dist/core/command-plugins/bundle.js +588 -0
  14. package/dist/core/command-plugins/endpoint.d.ts +24 -0
  15. package/dist/core/command-plugins/endpoint.js +51 -0
  16. package/dist/core/config.d.ts +233 -0
  17. package/dist/core/config.js +1120 -0
  18. package/dist/core/env-name.d.ts +6 -0
  19. package/dist/core/env-name.js +9 -0
  20. package/dist/core/errors.d.ts +38 -0
  21. package/dist/core/errors.js +90 -0
  22. package/dist/core/events/emit.d.ts +6 -0
  23. package/dist/core/events/emit.js +42 -0
  24. package/dist/core/events/envelope.d.ts +2 -0
  25. package/dist/core/events/envelope.js +84 -0
  26. package/dist/core/events/errors.d.ts +4 -0
  27. package/dist/core/events/errors.js +69 -0
  28. package/dist/core/events/operation-id.d.ts +4 -0
  29. package/dist/core/events/operation-id.js +24 -0
  30. package/dist/core/events/serialize.d.ts +4 -0
  31. package/dist/core/events/serialize.js +199 -0
  32. package/dist/core/events/source.d.ts +16 -0
  33. package/dist/core/events/source.js +31 -0
  34. package/dist/core/events/types.d.ts +68 -0
  35. package/dist/core/events/types.js +11 -0
  36. package/dist/core/exclusive-lock.d.ts +34 -0
  37. package/dist/core/exclusive-lock.js +197 -0
  38. package/dist/core/fs-utils.d.ts +44 -0
  39. package/dist/core/fs-utils.js +208 -0
  40. package/dist/core/help.d.ts +309 -0
  41. package/dist/core/help.js +406 -0
  42. package/dist/core/human/page-catalog.d.ts +57 -0
  43. package/dist/core/human/page-catalog.js +172 -0
  44. package/dist/core/installed-plugins.d.ts +2 -0
  45. package/dist/core/installed-plugins.js +79 -0
  46. package/dist/core/io.d.ts +122 -0
  47. package/dist/core/io.js +373 -0
  48. package/dist/core/keybindings/attach-control.d.ts +49 -0
  49. package/dist/core/keybindings/attach-control.js +42 -0
  50. package/dist/core/keybindings/catalog.d.ts +18 -0
  51. package/dist/core/keybindings/catalog.js +257 -0
  52. package/dist/core/keybindings/types.d.ts +42 -0
  53. package/dist/core/keybindings/types.js +1 -0
  54. package/dist/core/layout.d.ts +26 -0
  55. package/dist/core/layout.js +94 -0
  56. package/dist/core/locked-file.d.ts +27 -0
  57. package/dist/core/locked-file.js +118 -0
  58. package/dist/core/log.d.ts +9 -0
  59. package/dist/core/log.js +89 -0
  60. package/dist/core/manifest.d.ts +5 -0
  61. package/dist/core/manifest.js +15 -0
  62. package/dist/core/plugin-env.d.ts +8 -0
  63. package/dist/core/plugin-env.js +31 -0
  64. package/dist/core/plugin-extensions.d.ts +29 -0
  65. package/dist/core/plugin-extensions.js +191 -0
  66. package/dist/core/plugin-swap-lock.d.ts +9 -0
  67. package/dist/core/plugin-swap-lock.js +31 -0
  68. package/dist/core/preview-result-path.d.ts +4 -0
  69. package/dist/core/preview-result-path.js +26 -0
  70. package/dist/core/profiles/env-store.d.ts +22 -0
  71. package/dist/core/profiles/env-store.js +163 -0
  72. package/dist/core/profiles/fuzzy-match.d.ts +19 -0
  73. package/dist/core/profiles/fuzzy-match.js +92 -0
  74. package/dist/core/profiles/manifest.d.ts +120 -0
  75. package/dist/core/profiles/manifest.js +529 -0
  76. package/dist/core/rate-limit-scope.d.ts +25 -0
  77. package/dist/core/rate-limit-scope.js +64 -0
  78. package/dist/core/render.d.ts +12 -0
  79. package/dist/core/render.js +138 -0
  80. package/dist/core/resolver.d.ts +14 -0
  81. package/dist/core/resolver.js +111 -0
  82. package/dist/core/runtime/branded-host.d.ts +25 -0
  83. package/dist/core/runtime/branded-host.js +264 -0
  84. package/dist/core/runtime/broker/daemon-ops.d.ts +65 -0
  85. package/dist/core/runtime/broker/daemon-ops.js +177 -0
  86. package/dist/core/runtime/broker/signal-stream.d.ts +30 -0
  87. package/dist/core/runtime/broker/signal-stream.js +149 -0
  88. package/dist/core/scope.d.ts +32 -0
  89. package/dist/core/scope.js +184 -0
  90. package/dist/core/scoped-state/db.d.ts +17 -0
  91. package/dist/core/scoped-state/db.js +247 -0
  92. package/dist/core/scoped-state/migrate.d.ts +8 -0
  93. package/dist/core/scoped-state/migrate.js +187 -0
  94. package/dist/core/scoped-state/paths.d.ts +9 -0
  95. package/dist/core/scoped-state/paths.js +27 -0
  96. package/dist/core/scoped-state/profiles.d.ts +27 -0
  97. package/dist/core/scoped-state/profiles.js +93 -0
  98. package/dist/core/scoped-state/providers.d.ts +24 -0
  99. package/dist/core/scoped-state/providers.js +19 -0
  100. package/dist/core/scoped-state/schema.d.ts +6 -0
  101. package/dist/core/scoped-state/schema.js +43 -0
  102. package/dist/core/scoped-state/settings.d.ts +28 -0
  103. package/dist/core/scoped-state/settings.js +83 -0
  104. package/dist/core/spaces/open-beneath.d.ts +71 -0
  105. package/dist/core/spaces/open-beneath.js +581 -0
  106. package/dist/core/sqlite-statements.d.ts +4 -0
  107. package/dist/core/sqlite-statements.js +17 -0
  108. package/dist/core/subscription-state.d.ts +121 -0
  109. package/dist/core/subscription-state.js +287 -0
  110. package/dist/core/user-settings.d.ts +377 -0
  111. package/dist/core/user-settings.js +458 -0
  112. package/dist/daemon/broker-signals/bus.d.ts +30 -0
  113. package/dist/daemon/broker-signals/bus.js +87 -0
  114. package/dist/daemon/manage.d.ts +176 -0
  115. package/dist/daemon/manage.js +664 -0
  116. package/dist/daemon/pidfile.d.ts +8 -0
  117. package/dist/daemon/pidfile.js +37 -0
  118. package/dist/daemon/startup-policy.d.ts +1 -0
  119. package/dist/daemon/startup-policy.js +1 -0
  120. package/dist/native/linux.d.ts +29 -0
  121. package/dist/native/linux.js +20 -0
  122. package/dist/shared/env.d.ts +116 -0
  123. package/dist/shared/env.js +271 -0
  124. package/dist/shared/inbox-entry-body.d.ts +22 -0
  125. package/dist/shared/inbox-entry-body.js +116 -0
  126. package/dist/shared/working-activity.d.ts +9 -0
  127. package/dist/shared/working-activity.js +27 -0
  128. package/dist/types.d.ts +562 -0
  129. package/dist/types.js +186 -0
  130. package/package.json +1 -1
@@ -0,0 +1,391 @@
1
+ type PsProcessState = 'zombie' | 'gone' | 'live' | 'unknown';
2
+ /** True if a process with `pid` is currently alive. `kill(pid, 0)` provides the
3
+ * initial existence check; EPERM means the foreign process exists. For our
4
+ * processes, the later `ps` read is authoritative because it also distinguishes
5
+ * zombies and processes reaped between the two probes. Only an unavailable
6
+ * `ps` probe fails open to alive. A null/undefined pid reads dead. */
7
+ export declare function isPidAlive(pid: number | null | undefined): boolean;
8
+ /** Existence-only liveness for READ-ONLY display projections: the signal-0
9
+ * probe alone, no `ps`. A zombie reads alive here, which a dashboard column
10
+ * tolerates and which no lifecycle decision may rely on — revive, teardown,
11
+ * and every other authority stays on `isPidAlive`. Exists because the
12
+ * dashboard snapshot runs this per live row per request; a `ps` spawn each
13
+ * time was a third of daemon CPU on a large canvas. */
14
+ export declare function isPidPresent(pid: number | null | undefined): boolean;
15
+ /** THE single precision-aware identity compare, used EVERYWHERE two
16
+ * `composeIdentity` fingerprints are compared (this module's own
17
+ * `recordedPidLiveness`, `captureTeardownSnapshot`'s reuse check, and
18
+ * `killProcessTreePids`'s reuse check) — centralized so no call site can
19
+ * drift from this rule.
20
+ *
21
+ * Two hazards this closes, both in the false-DEAD direction (the dangerous
22
+ * one — a false DEAD lets `reviveNode`'s double-launch guard relaunch a
23
+ * SECOND broker onto a still-live session):
24
+ *
25
+ * 1. `composeIdentity` appends the `#<ticks>` Linux discriminator only when
26
+ * `/proc/<pid>/stat` happens to be readable at capture time — so the SAME
27
+ * live process can be recorded plain `lstart` on one capture and
28
+ * `lstart#ticks` on another (a transient `/proc` read failure, or
29
+ * capturing on a platform/container without procfs). A naive `===` reads
30
+ * that as a reuse (different string) and reports `dead` for a broker that
31
+ * never died.
32
+ * 2. `lstart` is NOT stable for a live process on every host. It is derived
33
+ * from `/proc/stat` btime (wall-clock boot epoch) + starttime jiffies, so
34
+ * a clock correction / suspend-resume shifts btime and re-anchors the
35
+ * SAME live process's `lstart` by seconds between two captures. Observed
36
+ * on Blaxel unikraft guests: pid+ticks identical across probes, `lstart`
37
+ * drifting +81s — a base-first compare then reads the live home-node
38
+ * broker as reused/dead every cycle, driving an uncapped revive loop.
39
+ *
40
+ * A third hazard the base itself once carried: `ticks` (jiffies-since-boot)
41
+ * RESET on every boot and can repeat across boots, so ticks-alone is only
42
+ * safe WITHIN one boot. `composeIdentity` therefore boot-SCOPES the base on
43
+ * Linux-with-procfs — the base IS the kernel `boot_id` UUID — so a
44
+ * new-format identity is `<bootId>#<ticks>` and equality means same boot AND
45
+ * same process. This closes the cross-boot false match at the root, without
46
+ * plumbing boot reconciliation ahead of every caller.
47
+ *
48
+ * Rule: split both on `#`.
49
+ * - BOTH sides carry `#ticks` AND both bases are `boot_id` UUIDs
50
+ * (new-format vs new-format): match iff base AND ticks are equal. A
51
+ * different boot_id with equal ticks is NOT a match (the cross-boot
52
+ * collision).
53
+ * - BOTH sides carry `#ticks` but a base is NOT a boot_id UUID on EITHER
54
+ * side — a LEGACY `lstart#ticks` row recorded before this repo boot-scoped
55
+ * the base, or a Linux host that has procfs ticks but no readable boot_id:
56
+ * ticks DECIDE it alone (the prior landed semantics). This is a NARROW
57
+ * format-migration path so an in-place upgrade never false-kills a live
58
+ * broker whose row still holds `lstart#ticks`; it is NOT a general
59
+ * fallback — new-format vs new-format always demands base+ticks above.
60
+ * - EITHER side lacks a valid `#ticks` suffix (macOS / no procfs / transient
61
+ * `/proc` miss / malformed suffix): fall back to the coarse base —
62
+ * matching iff the bases agree (fail-open to same-process, matching this
63
+ * module's universal discipline: only a POSITIVE mismatch counts as reuse,
64
+ * never an absence of evidence). */
65
+ export declare function identitiesMatch(a: string, b: string): boolean;
66
+ /** Three-valued liveness verdict for a RECORDED node pid, identity-guarded
67
+ * against PID REUSE using the launch-time baseline (`pi_pid_identity`,
68
+ * captured by `recordPid`). On a heavily-forking host — esp. Linux, where
69
+ * low pids recycle fast — a broker's recorded pid can be reused by an
70
+ * unrelated process (or the `ps` probe confirming it can itself
71
+ * intermittently fail) within milliseconds of the broker dying; collapsing
72
+ * "confirmed alive", "confirmed dead/reused", and "couldn't confirm either
73
+ * way" into one boolean forces every caller to fail open to ALIVE on the
74
+ * third case — which, upstream in the daemon's grace-clock bookkeeping, is
75
+ * worse than misreporting one tick: a single such read WIPES the daemon's
76
+ * memory that the node had already been observed dead, restarting the whole
77
+ * grace window from scratch (the confirmed Round-2 crash-revive wedge, in
78
+ * the pre-fleet daemon's liveness bookkeeping). Exposing the third state lets callers
79
+ * decide for themselves whether "can't confirm" should act like alive or
80
+ * dead for their own direction of risk.
81
+ *
82
+ * Without a snapshot this preserves the historical per-pid probes. With a
83
+ * snapshot it classifies process presence, zombie state, and identity from
84
+ * that one authoritative table read:
85
+ * - absent or zombie pid → `'dead'`. Load-bearing: a SIGKILLed detached
86
+ * broker awaiting reap must still be revived rather than read as live.
87
+ * - `expectedIdentity == null` (legacy row / launch capture failed) →
88
+ * `'alive'` (preserve pre-identity behavior — no baseline to check
89
+ * against).
90
+ * - `ps` probe itself failed (`capturePidIdentities` → `null`) →
91
+ * `'indeterminate'`. THIS is the branch the Round-2 fix changes: it used
92
+ * to fail open to `true` (alive) here, which is the confirmed root cause
93
+ * of the wedge.
94
+ * - probe succeeded but has no row for this pid (alive per signal-0 a
95
+ * moment ago, gone now) → `'dead'`.
96
+ * - probe succeeded and the identity is present → `'alive'` iff
97
+ * `identitiesMatch` says so, else `'dead'` (positively reused). */
98
+ export type RecordedPidLiveness = 'alive' | 'dead' | 'indeterminate';
99
+ export declare function recordedPidLiveness(pid: number | null | undefined, expectedIdentity: string | null | undefined, snapshot?: PsLivenessSnapshot | null): RecordedPidLiveness;
100
+ /** SIGTERM a process GROUP by pid (the negative-pid convention) — best-effort,
101
+ * swallowing ESRCH (already gone). Used by cron cancellation, timeout, and
102
+ * stale-lease recovery. Lives beside isPidAlive: canvas/ is
103
+ * the lowest shared layer every process-liveness/-teardown primitive sits at.
104
+ * Guards `pid` before signaling: never hit pid 0
105
+ * (which means "my own group") or a negative/non-integer value. */
106
+ export declare function killProcessGroup(pid: number, signal?: NodeJS.Signals): void;
107
+ /** The kernel `boot_id` (`/proc/sys/kernel/random/boot_id`) is stable for the
108
+ * whole lifetime of this process — it only changes on a kernel boot, which
109
+ * necessarily kills this process — so once a real value is read it is cached
110
+ * for good, rather than re-read for every row of a whole-process-table `ps`
111
+ * scan. Only SUCCESSFUL reads are cached forever: a `null` read is negative
112
+ * for `BOOT_ID_NEGATIVE_TTL_MS`, after which the next call retries — so a
113
+ * transient `/proc` miss on a boot_id-capable host still recovers (never
114
+ * permanently downgraded to legacy `lstart#ticks`), while a genuinely
115
+ * no-boot_id host is not hammered with a synchronous throwing read on every
116
+ * identity composition. `now` is injectable for tests. */
117
+ export declare function makeBootIdCache(read: () => string | null, now?: () => number, negativeTtlMs?: number): () => string | null;
118
+ /** Is `identity` a LEGACY (pre-boot-scoped) baseline — a `<lstart>#<ticks>` row
119
+ * whose base is NOT a boot_id UUID but which DOES carry ticks? These are the
120
+ * rows the one-time startup migration re-records once this platform can
121
+ * compose boot-scoped identities (see `migrateLegacyPidIdentities`). */
122
+ export declare function isLegacyTicksIdentity(identity: string): boolean;
123
+ /** Is `identity` a NEW-format boot-scoped baseline — `<bootId>#<ticks>` with a
124
+ * real boot_id UUID base AND ticks? The startup migration only re-records a
125
+ * legacy row when the pid's CURRENT identity comes out in this shape (i.e. the
126
+ * platform now composes new-format), never otherwise. */
127
+ export declare function isBootScopedIdentity(identity: string): boolean;
128
+ /** Compose the stable, BOOT-SCOPED process-identity fingerprint used
129
+ * everywhere in this module. ALWAYS used to build an identity string, never a
130
+ * raw field directly, so every identity captured anywhere (launch-time
131
+ * `recordPid`, the teardown snapshot, and the escalation-window re-check) is
132
+ * comparable apples-to-apples.
133
+ *
134
+ * Platform capability, not layered fallbacks — the base is the finest STABLE
135
+ * per-boot anchor available:
136
+ * - Linux-with-procfs (the production guest shape): `<bootId>#<ticks>` — the
137
+ * per-boot `boot_id` UUID plus the per-process jiffies-since-boot. Ticks
138
+ * reset per boot and can repeat, so scoping the base to the boot is what
139
+ * makes an equal-ticks compare safe ACROSS boots (see `identitiesMatch`).
140
+ * - Linux-with-procfs but no readable `boot_id` (if that combination
141
+ * exists): `<lstart>#<ticks>` — the prior landed behavior.
142
+ * - No procfs ticks (macOS / containers without procfs): `<lstart>` alone —
143
+ * unchanged coarse behavior. The base is only ever a `boot_id` when a
144
+ * `ticks` discriminator is ALSO present; a bare `boot_id` (shared by every
145
+ * process on the boot) would be a useless per-process identity. */
146
+ export declare function composeIdentity(pid: number, lstart: string, deps?: {
147
+ ticks?: string | null;
148
+ bootId?: string | null;
149
+ }): string;
150
+ /** One authoritative process-table sample. Supervision, broker census,
151
+ * teardown tree walks, identity guards, and zombie detection can all classify
152
+ * from this one read instead of spawning a `ps` probe per pid. `null` means the
153
+ * probe itself failed; callers preserve that as unknown rather than absence. */
154
+ export interface PsLivenessProcess {
155
+ zombie: boolean;
156
+ identity: string;
157
+ command: string;
158
+ }
159
+ export interface PsLivenessSnapshot {
160
+ processes: ReadonlyMap<number, PsLivenessProcess>;
161
+ childrenOf: ReadonlyMap<number, number[]>;
162
+ }
163
+ export declare function captureLivenessSnapshot(): PsLivenessSnapshot | null;
164
+ /** The same sample without blocking the event loop, for callers running inside
165
+ * crtrd's detached work: one `ps` read classifies every pid they care about,
166
+ * where a per-pid `spawnSync` probe would stall broker supervision and the API
167
+ * socket for the duration of each probe. `null` means the probe failed, which
168
+ * every caller must preserve as unknown rather than absence. */
169
+ export declare function captureLivenessSnapshotAsync(): Promise<PsLivenessSnapshot | null>;
170
+ /** Parsed `/proc/<pid>/stat`: `comm` is parenthesized and may itself contain
171
+ * spaces/parens, so fields are counted after the LAST `)`. */
172
+ interface ProcStat {
173
+ comm: string;
174
+ state: string;
175
+ ppid: number;
176
+ starttime: string;
177
+ }
178
+ export declare function parseProcStat(raw: string): ProcStat | null;
179
+ /** `/proc/<pid>/cmdline` (NUL-separated argv) as the single space-joined line
180
+ * `ps -o command=` prints; kernel threads and zombies, whose cmdline is
181
+ * empty, show as `[comm]` exactly as `ps` renders them. */
182
+ export declare function procCommandLine(rawCmdline: string, comm: string): string;
183
+ /** Where a procfs read comes from: the real `/proc` in production, a fake tree
184
+ * plus a fixed boot id in tests. */
185
+ interface ProcSource {
186
+ root: string;
187
+ bootId: () => string | null;
188
+ /** The uid this process runs as; defaults to `process.getuid()`. Tests inject
189
+ * one so a fake tree (owned by the test user) can hold a "foreign" entry. */
190
+ selfUid?: () => number | null;
191
+ }
192
+ declare function procProcessState(pid: number, src?: ProcSource): PsProcessState;
193
+ declare function procIdentities(pids: readonly number[], src?: ProcSource): Map<number, string> | null;
194
+ declare function procCommand(pid: number, src?: ProcSource): string | null;
195
+ /** One whole-table sample from procfs. Skipped as absent, as in a `ps` sample:
196
+ * a pid that vanishes between the listing and its read, and a pid whose stat
197
+ * is refused (EACCES/EPERM — `hidepid=1`) when its `/proc/<pid>` directory is
198
+ * owned by another uid (this user cannot see it, and `ps` would not list it).
199
+ * Every other read failure — including a refusal on a process we own or whose
200
+ * owner we cannot establish — fails the whole sample, which every caller
201
+ * treats as unknown. */
202
+ export declare function readProcfsSnapshot(src?: ProcSource): PsLivenessSnapshot | null;
203
+ /** The same sample without blocking the event loop (crtrd's detached work). */
204
+ export declare function readProcfsSnapshotAsync(src?: ProcSource): Promise<PsLivenessSnapshot | null>;
205
+ /** Every live process's pid, parent pid and full command line, for callers that
206
+ * only match command text or walk parentage (stray-daemon sweep, viewer-pane
207
+ * detection). `null` when no sample could be taken. Same backend rule as the
208
+ * rest of this module: `/proc` where usable, `ps` elsewhere. */
209
+ export declare function listProcessTable(): {
210
+ pid: number;
211
+ ppid: number;
212
+ command: string;
213
+ }[] | null;
214
+ /** Test seam: exercise the `/proc` backend against a fake tree. */
215
+ export declare const procfsBackendForTest: {
216
+ procIdentities: typeof procIdentities;
217
+ procProcessState: typeof procProcessState;
218
+ procCommand: typeof procCommand;
219
+ readProcfsSnapshot: typeof readProcfsSnapshot;
220
+ readProcfsSnapshotAsync: typeof readProcfsSnapshotAsync;
221
+ };
222
+ /** Every transitive descendant of `rootPid`, discovered by walking `ps`'s
223
+ * pid/ppid table (BFS) — NOT process-GROUP membership. The pi SDK's bash tool
224
+ * spawns its shell child with
225
+ * `detached: true` (verified in `@earendil-works/pi-agent-core`'s
226
+ * `harness/env/nodejs.js`), which calls `setsid()` and puts that child in a
227
+ * brand-new process GROUP of its own — so `kill(-rootPid)` never reaches it.
228
+ * `setsid()` changes only SID/PGID, NEVER ppid, so the child (and its own
229
+ * descendants) is still discoverable by walking parentage from `rootPid`, as
230
+ * long as `rootPid`'s OS process is still alive when we walk (a fully-dead
231
+ * parent is reparented away by the kernel immediately on exit, severing this
232
+ * link — the wedged-but-alive broker this fix targets never hits that). Best-
233
+ * effort: returns `[]` (never throws) on any `ps` failure, exactly like
234
+ * `killProcessGroup`'s ESRCH-swallowing. */
235
+ export declare function descendantPids(rootPid: number): number[];
236
+ /** Capture a portable process-identity fingerprint — `ps`'s `lstart` column,
237
+ * the process's full wall-clock start time — for each of `pids`, in ONE
238
+ * batched `ps -p <list>` call. `lstart` is a standard format keyword on BOTH
239
+ * BSD ps (macOS) and procps-ng (Linux), so this needs no `/proc` parsing or
240
+ * other Linux-only machinery. Used to guard against PID REUSE during the
241
+ * multi-second teardown escalation ladder: a
242
+ * captured pid can exit and the OS can recycle it for an unrelated process
243
+ * before the SIGKILL rung fires, and a bare `kill(pid, 0)` / `kill(pid, sig)`
244
+ * cannot tell the difference.
245
+ *
246
+ * Returns `null` — NOT an empty map — when the `ps` PROBE ITSELF failed (it
247
+ * could not be spawned, or was killed by the timeout): a probe failure means
248
+ * we have NO information, which is a fundamentally different outcome from a
249
+ * probe that SUCCEEDED and simply found no row for a given pid (that pid is
250
+ * genuinely gone). Collapsing these two into the same empty map is unsafe:
251
+ * `killProcessTreePids` cannot tell "pid confirmed gone" from "we don't know"
252
+ * without this
253
+ * distinction, and misreading a failed probe as positive proof of
254
+ * absence/reuse either signals a reused stranger or — worse — silently skips
255
+ * reaping a real live orphan. A successful call may still return a map with
256
+ * no entry for some/all of `pids` (those pids have no current row — genuinely
257
+ * gone); that is the valid empty-or-partial case and is NOT a failure.
258
+ *
259
+ * EXIT STATUS IS NOT THE FAILURE SIGNAL. `ps` exits 1 — on BOTH BSD ps and
260
+ * procps-ng — when it matched no process at all, which is precisely the
261
+ * "probe succeeded, that pid is gone" case, and exits 1 with PARTIAL stdout
262
+ * when only some of a batch's pids still exist. Reading a non-zero exit as a
263
+ * probe failure therefore turned every confirmed-dead pid into
264
+ * `'indeterminate'` (`recordedPidLiveness`), so a just-killed broker read as
265
+ * "can't confirm" — never as dead. Callers that fail open on indeterminate
266
+ * (`ensureAttach`'s revive decision, the daemon's exit policy) then treat a
267
+ * dead broker as live forever. Discriminate on whether the PROCESS RAN
268
+ * (`r.error`/`r.signal`) and whether it complained (`stderr`), never on what
269
+ * it exited with: a real argument/usage failure writes a diagnostic to stderr
270
+ * (`ps: process id too large`), while "matched nothing" is silent. */
271
+ export declare function capturePidIdentities(pids: readonly number[]): Map<number, string> | null;
272
+ /** Read one pid's current full command line for a destructive caller that
273
+ * must freshly re-confirm process ownership immediately before signaling.
274
+ * Failure and absence both return null, so uncertainty never authorizes a
275
+ * signal. */
276
+ export declare function capturePidCommand(pid: number): string | null;
277
+ /** The full pre-signal snapshot `teardown()` needs, taken ONCE, from ONE `ps`
278
+ * table: the process TREE rooted at `rootPid`
279
+ * (itself + every transitive descendant) alongside each member's identity
280
+ * fingerprint — PLUS, load-bearing, a launch-time-identity REUSE check on
281
+ * `rootPid` itself before the tree is even walked.
282
+ *
283
+ * The hazard this closes: the PID-reuse guard elsewhere in this module (see
284
+ * `killProcessTreePids`) only ever compared identities captured across the
285
+ * teardown ESCALATION window (a few seconds) — it had no idea whether
286
+ * `rootPid` (the broker pid recorded on the node row at LAUNCH time, possibly
287
+ * hours or days earlier) had ALREADY been recycled for an unrelated process
288
+ * before `teardown()` was ever called. In that case the naive snapshot would
289
+ * capture the STRANGER's current identity as the "expected" baseline and
290
+ * cheerfully SIGTERM/SIGKILL it and whatever the stranger's OWN process tree
291
+ * turns out to be — exactly the wrong-victim risk finding 1 targets.
292
+ *
293
+ * `expectedIdentity` is the LAUNCH-time identity recorded on the node row
294
+ * (`recordPid`'s `capturePidIdentities([pid])` snapshot, taken the instant the
295
+ * broker was spawned/bound) — `null` for a node booted before this field
296
+ * existed, or if that original capture itself failed. When `expectedIdentity`
297
+ * is present AND the CURRENT probe for `rootPid` succeeds AND finds a DIFFERENT
298
+ * identity, `rootPid` is provably a different OS process than the broker this
299
+ * node ever launched: `reused: true`, `tree: []`, `identities: null` — the
300
+ * caller must treat this exactly like "nothing to tear down", and must NOT
301
+ * walk `descendantPids` from this poisoned root (those "descendants" would be
302
+ * the STRANGER's children, not this node's). Every other case (no baseline,
303
+ * the pid is simply gone, the probe itself failed) falls through to the
304
+ * normal walk — fail-open, matching every other guard in this module: a
305
+ * MISMATCH is the only thing that skips, never an absence of evidence. */
306
+ export interface TeardownSnapshot {
307
+ /** `[rootPid, ...descendants]`, or `[]` when `reused` is true (nothing safe
308
+ * to signal) or `rootPid` itself is invalid. */
309
+ tree: number[];
310
+ /** Identity fingerprint per pid in `tree`, from the SAME `ps` table read as
311
+ * the tree walk — or `null` when that probe failed outright (fail-open: the
312
+ * caller's downstream `killProcessTreePids` signals unguarded in that case,
313
+ * same as an omitted `identities` arg). Always `null` when `reused` is true
314
+ * (there is no tree to guard). */
315
+ identities: Map<number, string> | null;
316
+ /** True iff `rootPid`'s CURRENT identity is POSITIVELY known to differ from
317
+ * `expectedIdentity` — the pid was already reused by an unrelated process
318
+ * before this snapshot was taken. Callers must skip ALL signaling when true. */
319
+ reused: boolean;
320
+ }
321
+ export declare function captureTeardownSnapshot(rootPid: number, expectedIdentity: string | null, excludePid?: number): TeardownSnapshot;
322
+ /** Best-effort signal against every pid in an ALREADY-CAPTURED tree snapshot
323
+ * (typically `[rootPid, ...descendantPids(rootPid)]`, taken ONCE by the
324
+ * caller before sending any signal — see the doc on `isProcessTreeAlive` for
325
+ * why re-deriving via `ps` after signaling starts is unsafe). Signals EACH
326
+ * pid's OWN process group (a detached SDK-spawned descendant is its own
327
+ * group leader — the pi bash tool's shell child)
328
+ * PLUS a plain `kill(pid)` on each (belt-and-suspenders for a pid that is
329
+ * NOT its own group leader). Every signal is best-effort — `killProcessGroup`
330
+ * swallows ESRCH and the plain `kill` here does too — so firing this against
331
+ * an already-fully-dead snapshot is a harmless no-op.
332
+ *
333
+ * `identities`, when supplied, is a
334
+ * `capturePidIdentities` snapshot taken alongside the pid list at the SAME
335
+ * moment — `null` if that INITIAL capture's `ps` probe itself failed (see
336
+ * `capturePidIdentities`'s doc). Before signaling EITHER the group or the
337
+ * plain kill for a pid, this re-derives CURRENT identities in one fresh
338
+ * batched call and compares — but ONLY skips on POSITIVE proof of reuse; any
339
+ * failure or absence of evidence falls through to the normal signal path
340
+ * (fail-open, matching this module's unguarded behavior):
341
+ *
342
+ * - baseline identity present AND the current probe SUCCEEDED AND the
343
+ * current identity for this pid is present AND DIFFERENT — the OS has
344
+ * recycled this pid for an unrelated process since the snapshot — SKIP
345
+ * (the sole skip condition; this is the actual reused-pid hazard finding
346
+ * 3 targets).
347
+ * - baseline identity present but the pid is now simply GONE (current
348
+ * probe succeeded, no row for this pid) — it died naturally in the
349
+ * meantime, not reused — SIGNAL as normal (harmless no-op on the plain
350
+ * kill; load-bearing for the group kill, which can still reach a
351
+ * surviving process that shares this now-dead pid's process GROUP).
352
+ * - baseline had NO identity at all for this pid (the pid was ALREADY dead
353
+ * at snapshot time — e.g. a broker that crashed before `teardown()` ever
354
+ * ran) — SIGNAL as normal regardless of the current probe: this is
355
+ * exactly how `killProcessGroup` on an already-dead root still reaches a
356
+ * live process sharing its process group (the pgid persists after the
357
+ * group leader exits; see `host-teardown-process-group.test.ts`'s "dead
358
+ * broker" case).
359
+ * - the INITIAL capture's probe failed outright (`identities === null`) —
360
+ * we have NO baseline to compare against for ANY pid — SIGNAL as normal
361
+ * for the whole list; the CURRENT probe is not even attempted (nothing
362
+ * to compare it to).
363
+ * - the baseline succeeded but the CURRENT probe (taken here) fails — we
364
+ * have NO current information — SIGNAL as normal for the whole list; a
365
+ * failed probe must never be read as "gone" (would risk reaching a
366
+ * reused stranger, the residual risk this guard cannot close) NOR as
367
+ * "reused" (would wrongly disable the reap for every real live pid).
368
+ *
369
+ * Callers that omit `identities` entirely (pre-existing call sites) keep the
370
+ * prior unguarded behavior. */
371
+ export declare function killProcessTreePids(pids: readonly number[], signal?: NodeJS.Signals, identities?: ReadonlyMap<number, string> | null): void;
372
+ /** Is `rootPid`'s process TREE — itself or any CURRENT descendant — still
373
+ * alive, re-deriving the descendant set fresh via `ps` on every call? Safe
374
+ * ONLY before any signal has been sent to this tree. Once a signal kills the
375
+ * root (it's typically its own process-group leader — see `launch()` in
376
+ * host.ts), the kernel reparents any surviving child away from `rootPid`
377
+ * IMMEDIATELY (standard orphan handling, confirmed empirically: a killed
378
+ * parent's child shows `ppid=1` within the same tick, not lazily) —
379
+ * silently "losing" that child from a fresh `descendantPids` walk even
380
+ * though it is still very much alive. Use this ONLY to watch a tree you have
381
+ * not touched yet (e.g. waiting out a graceful-shutdown grace window); once
382
+ * you start signaling, capture the pid list ONCE and check it with
383
+ * `isAnyPidAlive` instead — see `killProcessTreePids`. */
384
+ export declare function isProcessTreeAlive(rootPid: number): boolean;
385
+ /** Is any pid in an ALREADY-CAPTURED pid list (see `killProcessTreePids`)
386
+ * still alive? Pure signal-0 check against the fixed list — NEVER re-walks
387
+ * `ps` — the correct liveness check once you've started signaling a tree
388
+ * (see `isProcessTreeAlive`'s doc for why re-deriving after that point would
389
+ * silently drop a still-alive orphan). */
390
+ export declare function isAnyPidAlive(pids: readonly number[]): boolean;
391
+ export {};