Skip to content

Commit 9a8da65

Browse files
committed
fix: harden legacy lock migration probes
Fail closed on contradictory or incomplete pgrep, lsof, and Herdr evidence before archiving a v0.1 lock. Bump the plugin to v0.2.4.
1 parent 99970ac commit 9a8da65

6 files changed

Lines changed: 362 additions & 51 deletions

File tree

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,10 @@ the plugin-native `omp-fleet-supervision` skill installed with Fleet below.
5555

5656
## Install the plugin and skill
5757

58-
Install the immutable v0.2.3 Git tag directly over HTTPS:
58+
Install the immutable v0.2.4 Git tag directly over HTTPS:
5959

6060
```sh
61-
omp plugin install 'git+https://github.com/ericjuta/omp-fleet.git#v0.2.3'
61+
omp plugin install 'git+https://github.com/ericjuta/omp-fleet.git#v0.2.4'
6262
```
6363

6464
This single command installs both the Fleet extension and its packaged

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@ericjuta/omp-fleet",
3-
"version": "0.2.3",
3+
"version": "0.2.4",
44
"description": "Bounded Herdr supervisor sidecar control for Oh My Pi",
55
"license": "MIT",
66
"type": "module",

src/migrate-lock-command.ts

Lines changed: 36 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,6 @@ import {
1515
} from "./store.ts";
1616
import { isTerminalLifecycle } from "./types.ts";
1717

18-
const SIDECAR_NEEDLES = [
19-
"omp-fleet/src/sidecar.ts",
20-
"@ericjuta/omp-fleet/src/sidecar.ts",
21-
];
22-
2318
export interface LegacyLockMigrationDeps {
2419
readonly runner?: CommandRunner;
2520
readonly herdr?: Pick<HerdrClient, "inspectPane">;
@@ -52,23 +47,30 @@ async function runCaptured(
5247
}
5348
}
5449

55-
function parsePidLines(text: string): string[] {
50+
function parsePidOutput(
51+
text: string,
52+
label: string,
53+
requireBarePid: boolean,
54+
): string[] {
5655
const pids: string[] = [];
5756
for (const line of text.split("\n")) {
58-
const match = /^(\d+)\b/.exec(line.trim());
59-
if (match?.[1] !== undefined) pids.push(match[1]);
57+
if (line.length === 0) continue;
58+
const match = requireBarePid
59+
? /^(\d+)$/.exec(line)
60+
: /^(\d+)(?:\s|$)/.exec(line);
61+
if (match?.[1] === undefined) {
62+
throw new ProtocolStoreError(
63+
`legacy lock migration could not parse ${label}`,
64+
);
65+
}
66+
pids.push(match[1]);
6067
}
6168
return pids;
6269
}
6370

64-
function isDocumentedNoMatch(exitCode: number): boolean {
65-
return exitCode === 1;
66-
}
67-
6871
function requireCleanProbe(
6972
result: CommandResult | undefined,
7073
label: string,
71-
allowNoMatch: boolean,
7274
): CommandResult {
7375
if (result === undefined) {
7476
throw new ProtocolStoreError(
@@ -85,22 +87,35 @@ function requireCleanProbe(
8587
`legacy lock migration ${label} query was truncated`,
8688
);
8789
}
88-
if (result.stderr.trim().length > 0) {
90+
if (result.stderr.length > 0) {
8991
throw new ProtocolStoreError(
9092
`legacy lock migration ${label} query returned stderr`,
9193
);
9294
}
93-
if (
94-
result.exitCode !== 0 &&
95-
!(allowNoMatch && isDocumentedNoMatch(result.exitCode))
96-
) {
95+
if (result.exitCode !== 0 && result.exitCode !== 1) {
9796
throw new ProtocolStoreError(
9897
`legacy lock migration could not query ${label}`,
9998
);
10099
}
101100
return result;
102101
}
103102

103+
function parseProbePids(
104+
result: CommandResult,
105+
label: string,
106+
requireBarePid: boolean,
107+
): string[] {
108+
if (result.exitCode === 1) {
109+
if (result.stdout.length === 0) return [];
110+
} else if (result.stdout.length > 0) {
111+
const pids = parsePidOutput(result.stdout, label, requireBarePid);
112+
if (pids.length > 0) return pids;
113+
}
114+
throw new ProtocolStoreError(
115+
`legacy lock migration ${label} query returned contradictory output`,
116+
);
117+
}
118+
104119
export async function collectLegacyLockLiveEvidence(
105120
storeRoot: string,
106121
deps: LegacyLockMigrationDeps = {},
@@ -117,26 +132,14 @@ export async function collectLegacyLockLiveEvidence(
117132
const pgrep = requireCleanProbe(
118133
await runCaptured(runner, "pgrep", ["-af", "sidecar.ts"]),
119134
"sidecar PIDs",
120-
true,
121135
);
122-
const sidecarPids = pgrep.stdout
123-
.split("\n")
124-
.map((line) => line.trim())
125-
.filter(
126-
(line) =>
127-
line.length > 0 &&
128-
SIDECAR_NEEDLES.some((needle) => line.includes(needle)),
129-
)
130-
.flatMap((line) => parsePidLines(line));
136+
const sidecarPids = parseProbePids(pgrep, "sidecar PIDs", false);
131137

132138
const lsof = requireCleanProbe(
133139
await runCaptured(runner, "lsof", ["-t", "--", lockPath]),
134140
"lock holders",
135-
true,
136141
);
137-
const lockHolders = isDocumentedNoMatch(lsof.exitCode)
138-
? []
139-
: parsePidLines(lsof.stdout);
142+
const lockHolders = parseProbePids(lsof, "lock holder PIDs", true);
140143

141144
const store = new RunStore(root);
142145
const herdr = deps.herdr ?? new HerdrClient(runner);
@@ -153,9 +156,7 @@ export async function collectLegacyLockLiveEvidence(
153156
liveSupervisorPanes.push(manifest.supervisorPaneId);
154157
} catch (error) {
155158
const paneMissing =
156-
(error instanceof HerdrServerError &&
157-
error.code === "pane_not_found") ||
158-
(error instanceof Error && /\bpane_not_found\b/.test(error.message));
159+
error instanceof HerdrServerError && error.code === "pane_not_found";
159160
if (!paneMissing) {
160161
throw new ProtocolStoreError(
161162
"legacy lock migration could not inspect a recorded supervisor pane",

src/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { homedir } from "node:os";
33
import { isAbsolute, normalize, parse, resolve } from "node:path";
44

55
export const SCHEMA_VERSION = 1 as const;
6-
export const PLUGIN_VERSION = "0.2.3" as const;
6+
export const PLUGIN_VERSION = "0.2.4" as const;
77
export const REPORT_LIMIT = 64 as const;
88

99
export const RUN_LIFECYCLES = [

0 commit comments

Comments
 (0)