fix(verification): retain Hopper ownership evidence across owner exit (#5357)

Keep one ProcessOwnershipEvidenceOwner for each graceful/SIGKILL session and share it across active and terminal scoped scans. Require live token-verified evidence before stopping the owner, and retain structured cleanup failure diagnostics.

The real Ubuntu 24.04 Hopper clean-setup/CLI/MCP job passes, including owner exit, target reacquisition, idempotence and zero remaining descendants. Local verification passes 87 CI guards, 51 ownership controls, discovery of all 1,714 test files and full static checks.

Assisted-by: Reimu (gensokyo-skills:reimu-incident-triage)
This commit is contained in:
gensokyo-dev authored and GitHub committed 2026-10-20 05:52:06 +00:00
1 parent 4668cea246
commit 799e8265b2
1 file changed
+13 -11
+13 -11
View File
@@ -7,6 +7,7 @@ import { Client } from "@modelcontextprotocol/client";
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
import { verifyNoTokenOwnedProcesses } from "../../dist/process/ProcessOwnership.js";
import { ProcessOwnershipEvidenceOwner } from "../../dist/process/ProcessOwnershipEvidence.js";
import {
observeProcessStartIdentity,
signalProcessWithStartIdentity,
@@ -64,6 +65,7 @@ export async function verifyLinuxHopperOwnerExit({
const relation = {
leaderPid: ownerPid,
processGroupId: ownerProcess.processGroupId,
evidenceOwner: new ProcessOwnershipEvidenceOwner(),
};
requireMcpLifecycleResult(
await client.callTool(
@@ -93,16 +95,16 @@ export async function verifyLinuxHopperOwnerExit({
const runId = status.analysis_run?.run_id;
assert.equal(typeof runId, "string");
assert.equal(status.analysis_provider_binding?.provider.id, "hopper");
assert.equal(
(
await verifyNoTokenOwnedProcesses(
runId,
systemProcessOwnershipHost,
baseline,
relation,
)
).cleaned,
false,
const active = await verifyNoTokenOwnedProcesses(
runId,
systemProcessOwnershipHost,
baseline,
relation,
);
assert.equal(active.cleaned, false, JSON.stringify(active));
assert.ok(
relation.evidenceOwner.coverage.retainedOwned > 0,
`The active Hopper session has no token-verified process ownership evidence: ${JSON.stringify(active)}`,
);
if (mode === "graceful") await client.close();
else {
@@ -127,7 +129,7 @@ export async function verifyLinuxHopperOwnerExit({
if (cleanup.cleaned) break;
await delay(100);
} while (Date.now() < deadline);
assert.equal(cleanup.cleaned, true, cleanup.reason);
assert.equal(cleanup.cleaned, true, JSON.stringify(cleanup));
// The SIGKILL case must recover the stale lease for both target identities.
for (const target of [primary, secondary]) {
const result = await execFileAsync(