@descryy/runtime-controller 0.3.8 → 0.4.0
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.
- package/dist/attach-discovery.d.ts +94 -0
- package/dist/attach-discovery.d.ts.map +1 -0
- package/dist/attach-discovery.js +238 -0
- package/dist/attach-discovery.js.map +1 -0
- package/dist/attach-fencing.d.ts +109 -0
- package/dist/attach-fencing.d.ts.map +1 -0
- package/dist/attach-fencing.js +215 -0
- package/dist/attach-fencing.js.map +1 -0
- package/dist/capability-registry.d.ts +9 -0
- package/dist/capability-registry.d.ts.map +1 -0
- package/dist/capability-registry.js +32 -0
- package/dist/capability-registry.js.map +1 -0
- package/dist/collector-version.d.ts +3 -0
- package/dist/collector-version.d.ts.map +1 -0
- package/dist/collector-version.js +5 -0
- package/dist/collector-version.js.map +1 -0
- package/dist/container-sandbox.d.ts +104 -0
- package/dist/container-sandbox.d.ts.map +1 -0
- package/dist/container-sandbox.js +152 -0
- package/dist/container-sandbox.js.map +1 -0
- package/dist/controller.d.ts +152 -0
- package/dist/controller.d.ts.map +1 -0
- package/dist/controller.js +464 -0
- package/dist/controller.js.map +1 -0
- package/dist/dependency-version-check.d.ts +36 -0
- package/dist/dependency-version-check.d.ts.map +1 -0
- package/dist/dependency-version-check.js +72 -0
- package/dist/dependency-version-check.js.map +1 -0
- package/dist/env.d.ts +10 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +12 -0
- package/dist/env.js.map +1 -0
- package/dist/environment-metadata.d.ts +15 -0
- package/dist/environment-metadata.d.ts.map +1 -0
- package/dist/environment-metadata.js +63 -0
- package/dist/environment-metadata.js.map +1 -0
- package/dist/environment-version-check.d.ts +40 -0
- package/dist/environment-version-check.d.ts.map +1 -0
- package/dist/environment-version-check.js +176 -0
- package/dist/environment-version-check.js.map +1 -0
- package/dist/execution-safety.d.ts +96 -0
- package/dist/execution-safety.d.ts.map +1 -0
- package/dist/execution-safety.js +140 -0
- package/dist/execution-safety.js.map +1 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/orchestration.d.ts +55 -0
- package/dist/orchestration.d.ts.map +1 -0
- package/dist/orchestration.js +219 -0
- package/dist/orchestration.js.map +1 -0
- package/dist/preflight.d.ts +122 -0
- package/dist/preflight.d.ts.map +1 -0
- package/dist/preflight.js +177 -0
- package/dist/preflight.js.map +1 -0
- package/dist/process-collector.d.ts +20 -0
- package/dist/process-collector.d.ts.map +1 -0
- package/dist/process-collector.js +116 -0
- package/dist/process-collector.js.map +1 -0
- package/dist/process-identity.d.ts +46 -0
- package/dist/process-identity.d.ts.map +1 -0
- package/dist/process-identity.js +186 -0
- package/dist/process-identity.js.map +1 -0
- package/dist/process-manager.d.ts +137 -0
- package/dist/process-manager.d.ts.map +1 -0
- package/dist/process-manager.js +382 -0
- package/dist/process-manager.js.map +1 -0
- package/dist/readiness.d.ts +122 -0
- package/dist/readiness.d.ts.map +1 -0
- package/dist/readiness.js +214 -0
- package/dist/readiness.js.map +1 -0
- package/dist/sandbox.d.ts +94 -0
- package/dist/sandbox.d.ts.map +1 -0
- package/dist/sandbox.js +228 -0
- package/dist/sandbox.js.map +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Attach-mode discovery (H-attach-log-discovery): answers "what pid, if any,
|
|
3
|
+
* is listening on this port" and "where does that pid's own output go"
|
|
4
|
+
* from process/kernel state alone -- never by inferring what a framework
|
|
5
|
+
* logs, only by reading what the OS already knows. This is what lets
|
|
6
|
+
* `observe`'s attach mode work without the caller supplying a pid or a log
|
|
7
|
+
* file (`descry-core`'s own observe tool composes these two, once its pin on
|
|
8
|
+
* this package moves past the version that added them).
|
|
9
|
+
*
|
|
10
|
+
* Two independent lookups:
|
|
11
|
+
* - `findPortOwner` (port -> pid): reuses the same `/proc/net/tcp{,6}` +
|
|
12
|
+
* per-pid fd-table matching `process-identity.ts`'s `verifyListeningSocketOwner`
|
|
13
|
+
* already does for a DIFFERENT question (does a KNOWN pid own a port) --
|
|
14
|
+
* this asks the converse (which pid, if any, owns it), so a second, small,
|
|
15
|
+
* pure copy of the inode-matching core lives here rather than importing
|
|
16
|
+
* that module's private helpers across a real seam boundary (this file's
|
|
17
|
+
* own `reader` injection point, `process-identity.ts` has none).
|
|
18
|
+
* - `resolveLogSourceForPid` (pid -> log source descriptor): `readlink`s the
|
|
19
|
+
* pid's own fd 1 and fd 2. A regular file is the file-attach path this
|
|
20
|
+
* repo already tails with fencing (`attachManagedProcess`/
|
|
21
|
+
* `attachToRunningProcess`) -- returned as a path for the caller to feed
|
|
22
|
+
* into that machinery, not re-implemented here. A pipe or socket (a shell
|
|
23
|
+
* redirect, a container runtime, a terminal) can't be read by another
|
|
24
|
+
* process directly, so this falls back to the pid's own cgroup membership:
|
|
25
|
+
* a Docker container's cgroup path names it, a systemd unit's cgroup path
|
|
26
|
+
* names it. Building the actual stream source for those two
|
|
27
|
+
* (`docker logs -f` / `journalctl -f`) is `@descryy/runtime-orchestrator`'s
|
|
28
|
+
* job (`attach-log-sources.ts`) -- this package doesn't depend on the
|
|
29
|
+
* `ProcessOutputSource` contract at all, and doesn't need to.
|
|
30
|
+
*
|
|
31
|
+
* **Injectable `ProcReader`, not real `/proc`, in tests.** Real Docker/systemd
|
|
32
|
+
* cgroup membership can't be fabricated hermetically against the real
|
|
33
|
+
* filesystem, and even the plain-file/plain-port cases are easier to prove
|
|
34
|
+
* correct against a fake than a real, racy `/proc`. Both functions default to
|
|
35
|
+
* `REAL_PROC_READER` and take an injected one for tests.
|
|
36
|
+
*/
|
|
37
|
+
/** The subset of filesystem reads this module needs from `/proc` -- injectable so tests never touch the real one. */
|
|
38
|
+
export interface ProcReader {
|
|
39
|
+
readFile(path: string): string;
|
|
40
|
+
readdir(path: string): string[];
|
|
41
|
+
readlink(path: string): string;
|
|
42
|
+
/** `"dev:ino"` when `path` (symlinks followed) is a regular file, else `null` -- never a throw. */
|
|
43
|
+
regularFileIdentity(path: string): string | null;
|
|
44
|
+
}
|
|
45
|
+
export declare const REAL_PROC_READER: ProcReader;
|
|
46
|
+
export type PortOwnerLookup = {
|
|
47
|
+
readonly outcome: "found";
|
|
48
|
+
readonly pid: number;
|
|
49
|
+
} | {
|
|
50
|
+
readonly outcome: "nothing-listening";
|
|
51
|
+
} | {
|
|
52
|
+
readonly outcome: "several-owners";
|
|
53
|
+
readonly pids: readonly number[];
|
|
54
|
+
} | {
|
|
55
|
+
readonly outcome: "owned-by-docker-proxy";
|
|
56
|
+
readonly pid: number;
|
|
57
|
+
} | {
|
|
58
|
+
readonly outcome: "proc-unreadable";
|
|
59
|
+
readonly reason: string;
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Who, if anyone, is listening on `port` right now -- a real kernel-state
|
|
63
|
+
* lookup (`/proc/net/tcp{,6}` plus each candidate pid's own fd table), not a
|
|
64
|
+
* guess. `owned-by-docker-proxy` is named separately from `found` rather than
|
|
65
|
+
* silently returned as one: a published Docker port is commonly fronted by a
|
|
66
|
+
* host-side `docker-proxy` process, not the container's own pid, and treating
|
|
67
|
+
* that pid as the target would attach to the wrong process entirely.
|
|
68
|
+
*/
|
|
69
|
+
export declare function findPortOwner(port: number, reader?: ProcReader, platform?: NodeJS.Platform): PortOwnerLookup;
|
|
70
|
+
export type LogSourceDescriptor = {
|
|
71
|
+
readonly kind: "file";
|
|
72
|
+
readonly path: string;
|
|
73
|
+
} | {
|
|
74
|
+
readonly kind: "docker";
|
|
75
|
+
readonly container: string;
|
|
76
|
+
} | {
|
|
77
|
+
readonly kind: "systemd";
|
|
78
|
+
readonly unit: string;
|
|
79
|
+
} | {
|
|
80
|
+
readonly kind: "none";
|
|
81
|
+
readonly reason: string;
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* Where `pid`'s own log output can be read from, without the caller naming a
|
|
85
|
+
* pid or a log file. `file` reuses this repo's existing fenced file-tail path
|
|
86
|
+
* (`attachManagedProcess`/`attachToRunningProcess`) -- returned as a path for
|
|
87
|
+
* the caller to feed into that machinery, not re-implemented here.
|
|
88
|
+
* `docker`/`systemd` name a stream-only source `@descryy/runtime-orchestrator`
|
|
89
|
+
* builds directly -- neither has pre-existing content to fence, since both
|
|
90
|
+
* start reading from "now" (see `DiscoveredAttachLogSource`'s own doc,
|
|
91
|
+
* `@descryy/runtime-contracts`).
|
|
92
|
+
*/
|
|
93
|
+
export declare function resolveLogSourceForPid(pid: number, reader?: ProcReader, platform?: NodeJS.Platform): LogSourceDescriptor;
|
|
94
|
+
//# sourceMappingURL=attach-discovery.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attach-discovery.d.ts","sourceRoot":"","sources":["../src/attach-discovery.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAIH,qHAAqH;AACrH,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAChC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B,mGAAmG;IACnG,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;CAClD;AAED,eAAO,MAAM,gBAAgB,EAAE,UAY9B,CAAC;AA6DF,MAAM,MAAM,eAAe,GACvB;IAAE,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GACnD;IAAE,QAAQ,CAAC,OAAO,EAAE,mBAAmB,CAAA;CAAE,GACzC;IAAE,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,GACxE;IAAE,QAAQ,CAAC,OAAO,EAAE,uBAAuB,CAAC;IAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GACnE;IAAE,QAAQ,CAAC,OAAO,EAAE,iBAAiB,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAErE;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,GAAE,UAA6B,EAAE,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAAG,eAAe,CA0BhJ;AAED,MAAM,MAAM,mBAAmB,GAC3B;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAChD;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GACvD;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACnD;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAiDvD;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,GAAE,UAA6B,EAAE,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAAG,mBAAmB,CA4B5J"}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Attach-mode discovery (H-attach-log-discovery): answers "what pid, if any,
|
|
3
|
+
* is listening on this port" and "where does that pid's own output go"
|
|
4
|
+
* from process/kernel state alone -- never by inferring what a framework
|
|
5
|
+
* logs, only by reading what the OS already knows. This is what lets
|
|
6
|
+
* `observe`'s attach mode work without the caller supplying a pid or a log
|
|
7
|
+
* file (`descry-core`'s own observe tool composes these two, once its pin on
|
|
8
|
+
* this package moves past the version that added them).
|
|
9
|
+
*
|
|
10
|
+
* Two independent lookups:
|
|
11
|
+
* - `findPortOwner` (port -> pid): reuses the same `/proc/net/tcp{,6}` +
|
|
12
|
+
* per-pid fd-table matching `process-identity.ts`'s `verifyListeningSocketOwner`
|
|
13
|
+
* already does for a DIFFERENT question (does a KNOWN pid own a port) --
|
|
14
|
+
* this asks the converse (which pid, if any, owns it), so a second, small,
|
|
15
|
+
* pure copy of the inode-matching core lives here rather than importing
|
|
16
|
+
* that module's private helpers across a real seam boundary (this file's
|
|
17
|
+
* own `reader` injection point, `process-identity.ts` has none).
|
|
18
|
+
* - `resolveLogSourceForPid` (pid -> log source descriptor): `readlink`s the
|
|
19
|
+
* pid's own fd 1 and fd 2. A regular file is the file-attach path this
|
|
20
|
+
* repo already tails with fencing (`attachManagedProcess`/
|
|
21
|
+
* `attachToRunningProcess`) -- returned as a path for the caller to feed
|
|
22
|
+
* into that machinery, not re-implemented here. A pipe or socket (a shell
|
|
23
|
+
* redirect, a container runtime, a terminal) can't be read by another
|
|
24
|
+
* process directly, so this falls back to the pid's own cgroup membership:
|
|
25
|
+
* a Docker container's cgroup path names it, a systemd unit's cgroup path
|
|
26
|
+
* names it. Building the actual stream source for those two
|
|
27
|
+
* (`docker logs -f` / `journalctl -f`) is `@descryy/runtime-orchestrator`'s
|
|
28
|
+
* job (`attach-log-sources.ts`) -- this package doesn't depend on the
|
|
29
|
+
* `ProcessOutputSource` contract at all, and doesn't need to.
|
|
30
|
+
*
|
|
31
|
+
* **Injectable `ProcReader`, not real `/proc`, in tests.** Real Docker/systemd
|
|
32
|
+
* cgroup membership can't be fabricated hermetically against the real
|
|
33
|
+
* filesystem, and even the plain-file/plain-port cases are easier to prove
|
|
34
|
+
* correct against a fake than a real, racy `/proc`. Both functions default to
|
|
35
|
+
* `REAL_PROC_READER` and take an injected one for tests.
|
|
36
|
+
*/
|
|
37
|
+
import { readFileSync, readdirSync, readlinkSync, statSync } from "node:fs";
|
|
38
|
+
export const REAL_PROC_READER = {
|
|
39
|
+
readFile: (path) => readFileSync(path, "utf8"),
|
|
40
|
+
readdir: (path) => readdirSync(path),
|
|
41
|
+
readlink: (path) => readlinkSync(path),
|
|
42
|
+
regularFileIdentity: (path) => {
|
|
43
|
+
try {
|
|
44
|
+
const stat = statSync(path);
|
|
45
|
+
return stat.isFile() ? `${String(stat.dev)}:${String(stat.ino)}` : null;
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
function hexPort(port) {
|
|
53
|
+
return port.toString(16).toUpperCase().padStart(4, "0");
|
|
54
|
+
}
|
|
55
|
+
/** Every inode `/proc/net/tcp{,6}` records as LISTENing on `port`. A small, deliberate second copy of `process-identity.ts`'s private `listenInodesForPort` -- see this file's own header for why it isn't imported. */
|
|
56
|
+
function listenInodesForPort(reader, port) {
|
|
57
|
+
const inodes = new Set();
|
|
58
|
+
const targetPort = hexPort(port);
|
|
59
|
+
const TCP_LISTEN_STATE = "0A";
|
|
60
|
+
for (const path of ["/proc/net/tcp", "/proc/net/tcp6"]) {
|
|
61
|
+
let content;
|
|
62
|
+
try {
|
|
63
|
+
content = reader.readFile(path);
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
for (const line of content.split("\n").slice(1)) {
|
|
69
|
+
const fields = line.trim().split(/\s+/);
|
|
70
|
+
const localAddress = fields[1];
|
|
71
|
+
const state = fields[3];
|
|
72
|
+
const inode = fields[9];
|
|
73
|
+
if (localAddress === undefined || state === undefined || inode === undefined)
|
|
74
|
+
continue;
|
|
75
|
+
if (state !== TCP_LISTEN_STATE)
|
|
76
|
+
continue;
|
|
77
|
+
if (localAddress.split(":")[1] === targetPort)
|
|
78
|
+
inodes.add(inode);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return inodes;
|
|
82
|
+
}
|
|
83
|
+
/** Whether `pid`'s fd table holds one of `inodes`. `false`, never a throw, for a pid whose fd table cannot be read (exited mid-scan, or a permission boundary) -- the same benign-race tolerance this repo's other `/proc` scanners apply. */
|
|
84
|
+
function ownsOneOf(reader, pid, inodes) {
|
|
85
|
+
let fds;
|
|
86
|
+
try {
|
|
87
|
+
fds = reader.readdir(`/proc/${pid}/fd`);
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
const SOCKET_FD_PATTERN = /^socket:\[(\d+)\]$/;
|
|
93
|
+
for (const fd of fds) {
|
|
94
|
+
let link;
|
|
95
|
+
try {
|
|
96
|
+
link = reader.readlink(`/proc/${pid}/fd/${fd}`);
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
const match = SOCKET_FD_PATTERN.exec(link);
|
|
102
|
+
if (match !== null && inodes.has(match[1]))
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
function commOf(reader, pid) {
|
|
108
|
+
try {
|
|
109
|
+
return reader.readFile(`/proc/${pid}/comm`).trim();
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Who, if anyone, is listening on `port` right now -- a real kernel-state
|
|
117
|
+
* lookup (`/proc/net/tcp{,6}` plus each candidate pid's own fd table), not a
|
|
118
|
+
* guess. `owned-by-docker-proxy` is named separately from `found` rather than
|
|
119
|
+
* silently returned as one: a published Docker port is commonly fronted by a
|
|
120
|
+
* host-side `docker-proxy` process, not the container's own pid, and treating
|
|
121
|
+
* that pid as the target would attach to the wrong process entirely.
|
|
122
|
+
*/
|
|
123
|
+
export function findPortOwner(port, reader = REAL_PROC_READER, platform = process.platform) {
|
|
124
|
+
if (platform !== "linux") {
|
|
125
|
+
return { outcome: "proc-unreadable", reason: `port ownership lookup requires /proc, which is Linux-only; not available on "${platform}"` };
|
|
126
|
+
}
|
|
127
|
+
const inodes = listenInodesForPort(reader, port);
|
|
128
|
+
if (inodes.size === 0)
|
|
129
|
+
return { outcome: "nothing-listening" };
|
|
130
|
+
let pidEntries;
|
|
131
|
+
try {
|
|
132
|
+
pidEntries = reader.readdir("/proc").filter((entry) => /^\d+$/.test(entry));
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
return {
|
|
136
|
+
outcome: "proc-unreadable",
|
|
137
|
+
reason: `could not list /proc to find the process listening on port ${String(port)}: ${error instanceof Error ? error.message : String(error)}`,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
const owners = pidEntries.filter((pid) => ownsOneOf(reader, pid, inodes)).map(Number);
|
|
141
|
+
if (owners.length === 0)
|
|
142
|
+
return { outcome: "nothing-listening" };
|
|
143
|
+
if (owners.length > 1)
|
|
144
|
+
return { outcome: "several-owners", pids: owners };
|
|
145
|
+
const pid = owners[0];
|
|
146
|
+
if (commOf(reader, String(pid)) === "docker-proxy") {
|
|
147
|
+
return { outcome: "owned-by-docker-proxy", pid };
|
|
148
|
+
}
|
|
149
|
+
return { outcome: "found", pid };
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* The path of the regular file `pid` holds open on `fd`, or `null`. An absolute link target
|
|
153
|
+
* is not enough: a terminal (`/dev/pts/3`) and `/dev/null` are absolute too, and tailing a
|
|
154
|
+
* terminal device would read keystrokes, not logs. So the open file itself (`/proc/<pid>/fd/N`,
|
|
155
|
+
* which stat follows to the real inode) must be a regular file, and the path must still name
|
|
156
|
+
* that same inode -- after a log rotation the process keeps writing the old file while the path
|
|
157
|
+
* names a new one, and tailing the path would read a file nobody is writing to.
|
|
158
|
+
*/
|
|
159
|
+
function fileTargetOf(reader, pid, fd) {
|
|
160
|
+
const fdPath = `/proc/${String(pid)}/fd/${String(fd)}`;
|
|
161
|
+
let link;
|
|
162
|
+
try {
|
|
163
|
+
link = reader.readlink(fdPath);
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
if (!link.startsWith("/"))
|
|
169
|
+
return null;
|
|
170
|
+
const held = reader.regularFileIdentity(fdPath);
|
|
171
|
+
if (held === null)
|
|
172
|
+
return null;
|
|
173
|
+
return reader.regularFileIdentity(link) === held ? link : null;
|
|
174
|
+
}
|
|
175
|
+
/** `/proc/<pid>/cgroup`'s docker container id, from either the legacy `.../docker/<id>` path or the cgroup-v2 `.../docker-<id>.scope` form. `null` when neither pattern is present. */
|
|
176
|
+
function dockerContainerFromCgroup(cgroup) {
|
|
177
|
+
const legacy = /\/docker\/([0-9a-f]{12,64})/.exec(cgroup);
|
|
178
|
+
if (legacy?.[1] !== undefined)
|
|
179
|
+
return legacy[1];
|
|
180
|
+
const scoped = /docker-([0-9a-f]{12,64})\.scope/.exec(cgroup);
|
|
181
|
+
return scoped?.[1] ?? null;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* The system unit `pid` runs as, e.g. `myapp.service` from `0::/system.slice/myapp.service`.
|
|
185
|
+
* Only when that unit is the process's own cgroup leaf under `system.slice`: an app started
|
|
186
|
+
* from a desktop terminal sits under `user@1000.service/...`, the per-user manager, whose
|
|
187
|
+
* journal is every desktop app's output, and a user service's journal is not what
|
|
188
|
+
* `journalctl -u` reads. Either would attribute someone else's logs to the target, so
|
|
189
|
+
* both are `null` here and disclosed as unreadable rather than guessed.
|
|
190
|
+
*/
|
|
191
|
+
function systemdUnitFromCgroup(cgroup) {
|
|
192
|
+
for (const line of cgroup.split("\n")) {
|
|
193
|
+
const path = line.slice(line.indexOf(":", line.indexOf(":") + 1) + 1);
|
|
194
|
+
const match = /^\/system\.slice\/(?:[\w.@-]+\.slice\/)*([\w.@-]+\.service)$/.exec(path);
|
|
195
|
+
if (match?.[1] !== undefined)
|
|
196
|
+
return match[1];
|
|
197
|
+
}
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Where `pid`'s own log output can be read from, without the caller naming a
|
|
202
|
+
* pid or a log file. `file` reuses this repo's existing fenced file-tail path
|
|
203
|
+
* (`attachManagedProcess`/`attachToRunningProcess`) -- returned as a path for
|
|
204
|
+
* the caller to feed into that machinery, not re-implemented here.
|
|
205
|
+
* `docker`/`systemd` name a stream-only source `@descryy/runtime-orchestrator`
|
|
206
|
+
* builds directly -- neither has pre-existing content to fence, since both
|
|
207
|
+
* start reading from "now" (see `DiscoveredAttachLogSource`'s own doc,
|
|
208
|
+
* `@descryy/runtime-contracts`).
|
|
209
|
+
*/
|
|
210
|
+
export function resolveLogSourceForPid(pid, reader = REAL_PROC_READER, platform = process.platform) {
|
|
211
|
+
if (platform !== "linux") {
|
|
212
|
+
return { kind: "none", reason: `log-source discovery reads /proc, which is Linux-only; not available on "${platform}"` };
|
|
213
|
+
}
|
|
214
|
+
const filePath = fileTargetOf(reader, pid, 1) ?? fileTargetOf(reader, pid, 2);
|
|
215
|
+
if (filePath !== null)
|
|
216
|
+
return { kind: "file", path: filePath };
|
|
217
|
+
let cgroup;
|
|
218
|
+
try {
|
|
219
|
+
cgroup = reader.readFile(`/proc/${String(pid)}/cgroup`);
|
|
220
|
+
}
|
|
221
|
+
catch (error) {
|
|
222
|
+
return {
|
|
223
|
+
kind: "none",
|
|
224
|
+
reason: `pid ${String(pid)}'s stdout/stderr is neither a regular file nor a terminal/pipe this process can name a container or service for, and its cgroup membership could not be read: ${error instanceof Error ? error.message : String(error)}`,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
const container = dockerContainerFromCgroup(cgroup);
|
|
228
|
+
if (container !== null)
|
|
229
|
+
return { kind: "docker", container };
|
|
230
|
+
const unit = systemdUnitFromCgroup(cgroup);
|
|
231
|
+
if (unit !== null)
|
|
232
|
+
return { kind: "systemd", unit };
|
|
233
|
+
return {
|
|
234
|
+
kind: "none",
|
|
235
|
+
reason: `pid ${String(pid)}'s stdout/stderr is a pipe or terminal, not a regular file -- another process cannot read it, and its cgroup names neither a Docker container nor a systemd unit to follow instead.`,
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
//# sourceMappingURL=attach-discovery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attach-discovery.js","sourceRoot":"","sources":["../src/attach-discovery.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Attach fencing (RT-N3): the boundary between "content this run watched happen"
|
|
3
|
+
* and "content that already existed when this run started watching it."
|
|
4
|
+
*
|
|
5
|
+
* Both `orchestrator/src/attach-to-running-process.ts` and this package's own
|
|
6
|
+
* `attachManagedProcess` read a target's ENTIRE log file on every poll —
|
|
7
|
+
* necessary, since a plain file offers no delta API — which means the very
|
|
8
|
+
* first read hands back everything the target had already written before
|
|
9
|
+
* attach, indistinguishable from something that happens during this run
|
|
10
|
+
* unless something records the boundary.
|
|
11
|
+
*
|
|
12
|
+
* Real incident (`documents/plans/descry-uat-phase-4.md`, finding N3): a
|
|
13
|
+
* killed, orphaned FIRST process took its full timeout to exit and kept
|
|
14
|
+
* writing to the SAME log path a SECOND, successful process also used;
|
|
15
|
+
* attaching to the second process's pid read the first process's crash as if
|
|
16
|
+
* it were the second's own. Two independent fences close two independent
|
|
17
|
+
* parts of that gap:
|
|
18
|
+
*
|
|
19
|
+
* - `computeAttachFence` records, at the moment of attach, how much of the
|
|
20
|
+
* log file already existed (`attachOffsetBytes`, `preExistingLineCount`)
|
|
21
|
+
* and the target's own process start time (best-effort, Linux only) — the
|
|
22
|
+
* information a caller needs to mark already-written content instead of
|
|
23
|
+
* treating it as freshly observed.
|
|
24
|
+
* - `detectOtherLogWriters` answers a different question: is some OTHER
|
|
25
|
+
* process, right now, also holding this same log file open for writing?
|
|
26
|
+
* On Linux only, by scanning `/proc/*\/fd`. A genuinely shared log (e.g. a
|
|
27
|
+
* `docker compose` stack's combined stream) is a legitimate answer here,
|
|
28
|
+
* not refused — this function reports who else has the file open, and it
|
|
29
|
+
* is the caller's job to decide what that means for a given target.
|
|
30
|
+
*
|
|
31
|
+
* Neither function makes attach perfectly correct — a log file carries no
|
|
32
|
+
* per-line writer-pid metadata, so "verify every line against the declared
|
|
33
|
+
* pid" is not implementable over a plain text file. What these two give a
|
|
34
|
+
* caller together: the boundary of what this run actually watched happen,
|
|
35
|
+
* and a best-effort, honestly-labelled answer to "is anything else writing
|
|
36
|
+
* here right now."
|
|
37
|
+
*/
|
|
38
|
+
export type ProcessStartTimeResult = {
|
|
39
|
+
readonly ok: true;
|
|
40
|
+
readonly startTimeMs: number;
|
|
41
|
+
} | {
|
|
42
|
+
readonly ok: false;
|
|
43
|
+
readonly reason: string;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* `pid`'s own start time, as wall-clock epoch milliseconds — Linux only.
|
|
47
|
+
* `/proc/<pid>/stat` field 22 (`starttime`) is clock ticks since boot, not
|
|
48
|
+
* since epoch; converted via `/proc/uptime`'s boot-relative uptime, itself
|
|
49
|
+
* converted to an epoch instant from `Date.now()` at read time (so this is
|
|
50
|
+
* only as precise as the gap between the two reads, sub-millisecond in
|
|
51
|
+
* practice).
|
|
52
|
+
*
|
|
53
|
+
* The `comm` field (`/proc/pid/stat` field 2) can itself contain spaces and
|
|
54
|
+
* parentheses — parsed by finding the LAST `)` on the line, matching every
|
|
55
|
+
* real `/proc/pid/stat` parser: the kernel guarantees the true comm field
|
|
56
|
+
* ends at the last `)`, whatever it contains.
|
|
57
|
+
*/
|
|
58
|
+
export declare function getProcessStartTimeMs(pid: number, platform?: NodeJS.Platform): ProcessStartTimeResult;
|
|
59
|
+
export type OtherLogWriterCheck = {
|
|
60
|
+
readonly checked: true;
|
|
61
|
+
readonly otherWriterPids: readonly number[];
|
|
62
|
+
} | {
|
|
63
|
+
readonly checked: false;
|
|
64
|
+
readonly reason: string;
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* Every OTHER process (not `targetPid`) currently holding an open file
|
|
68
|
+
* descriptor to `logFilePath`, by scanning `/proc/*\/fd` and comparing each
|
|
69
|
+
* descriptor's real target against `logFilePath`'s own real path. Linux only.
|
|
70
|
+
*
|
|
71
|
+
* A non-empty result is not by itself a problem — a deliberately shared log
|
|
72
|
+
* (several processes in one `docker compose` stack writing one combined
|
|
73
|
+
* stream) is legitimate multi-writer use. This function only answers "who
|
|
74
|
+
* else has it open right now"; a caller decides what that means for a given
|
|
75
|
+
* target.
|
|
76
|
+
*
|
|
77
|
+
* `checked: false` (never a thrown exception) only for what stops the scan
|
|
78
|
+
* from running AT ALL: non-Linux, `/proc` itself unreadable, or the log path
|
|
79
|
+
* can't be resolved to a real path. A single pid's `/proc/<pid>/fd` being
|
|
80
|
+
* unreadable (exited mid-scan, or a permission boundary) is NOT that —
|
|
81
|
+
* skipped for that one pid only, the same benign-race tolerance
|
|
82
|
+
* `inferRespawnCommandFromProc` already applies to `/proc` reads elsewhere
|
|
83
|
+
* in this codebase.
|
|
84
|
+
*/
|
|
85
|
+
export declare function detectOtherLogWriters(logFilePath: string, targetPid: number, platform?: NodeJS.Platform): OtherLogWriterCheck;
|
|
86
|
+
export interface AttachFence {
|
|
87
|
+
/** The log file's size in bytes at the moment of attach — rotation detection compares this (or a later high-water mark) against the file's current size. */
|
|
88
|
+
readonly attachOffsetBytes: number;
|
|
89
|
+
/**
|
|
90
|
+
* How many complete lines already existed in the log file at the moment of
|
|
91
|
+
* attach. A source that yields lines in file order marks the first
|
|
92
|
+
* this-many `preExisting: true`. See `countCompleteLines` for the one
|
|
93
|
+
* disclosed edge case (a line torn exactly at the attach instant).
|
|
94
|
+
*/
|
|
95
|
+
readonly preExistingLineCount: number;
|
|
96
|
+
/** Best-effort, Linux-only; `ok: false` on any other platform or read failure — never blocks the attach itself. */
|
|
97
|
+
readonly processStartTime: ProcessStartTimeResult;
|
|
98
|
+
/** See `detectOtherLogWriters` — computed once, at the moment of attach, against the pid this fence was built for. */
|
|
99
|
+
readonly otherWriters: OtherLogWriterCheck;
|
|
100
|
+
readonly attachedAt: string;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Snapshots the fence at the moment of attach. Never throws: a log file that
|
|
104
|
+
* doesn't exist yet at attach time (a target that hasn't written anything) is
|
|
105
|
+
* an honest zero-backlog fence, not an error — the file-tail machinery
|
|
106
|
+
* downstream already tolerates a not-yet-existing path the same way.
|
|
107
|
+
*/
|
|
108
|
+
export declare function computeAttachFence(pid: number, logFilePath: string, platform?: NodeJS.Platform): AttachFence;
|
|
109
|
+
//# sourceMappingURL=attach-fencing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attach-fencing.d.ts","sourceRoot":"","sources":["../src/attach-fencing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAsBH,MAAM,MAAM,sBAAsB,GAAG;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAwB3I;;;;;;;;;;;;GAYG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAAG,sBAAsB,CAiCvH;AAED,MAAM,MAAM,mBAAmB,GAC3B;IAAE,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,eAAe,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,GACvE;IAAE,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAEzD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAAG,mBAAmB,CA+C/I;AAED,MAAM,WAAW,WAAW;IAC1B,4JAA4J;IAC5J,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC;;;;;OAKG;IACH,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;IACtC,mHAAmH;IACnH,QAAQ,CAAC,gBAAgB,EAAE,sBAAsB,CAAC;IAClD,sHAAsH;IACtH,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC;IAC3C,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAAG,WAAW,CAgB9H"}
|