From 799e8265b254e225dc1dabdd25e56f0ab7f86bd3 Mon Sep 17 00:00:00 2001 From: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com> Date: Tue, 20 Oct 2026 13:52:06 +0800 Subject: [PATCH] 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) --- scripts/lib/real-hopper-owner-exit.mjs | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/scripts/lib/real-hopper-owner-exit.mjs b/scripts/lib/real-hopper-owner-exit.mjs index ff59b95860..9cdf0d3cf8 100644 --- a/scripts/lib/real-hopper-owner-exit.mjs +++ b/scripts/lib/real-hopper-owner-exit.mjs @@ -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(