Files
rea/src/process/ProcessOwnership.sweepExit.test.ts
T
morluto 4af71a45f9 fix(process): reconcile native lifetime and selective Linux UID facts
Settle absent and zombie candidates before retaining baseline or token-read failures. Preserve fail-closed behavior for live and unknown generations, and stop retrying exited candidates.

Bind Linux effective UID to stat-bracketed status reads only for unreadable-token candidates, preserving partial identity facts and avoiding full-table UID enrichment.
2026-10-20 00:05:22 +08:00

75 lines
2.3 KiB
TypeScript

import { expect, it, vi } from "vitest";
import {
verifyNoTokenOwnedProcesses,
type ProcessIdentityObservation,
type ProcessOwnershipHost,
type ProcessTableEntry,
} from "./ProcessOwnership.js";
import { ProcessOwnershipEvidenceOwner } from "./ProcessOwnershipEvidence.js";
it.each(["absent", "zombie", "live", "unavailable"] as const)(
"settles a baseline candidate against its final native lifetime: %s",
async (lifetime) => {
const row: ProcessTableEntry = {
pid: 910,
parentPid: 1,
processGroupId: 910,
state: "S",
command: "baseline-neighbor",
};
let tokenRead = false;
const identity = (): ProcessIdentityObservation => {
if (lifetime === "absent")
return { state: "absent", reason: "not-in-snapshot" };
if (lifetime === "unavailable")
return { state: "unavailable", reason: "native identity denied" };
return {
state: "readable",
identity: "new-generation",
lifecycle: {
state: "observed",
lifecycle:
tokenRead && lifetime === "zombie" ? "zombie" : "non-zombie",
},
relation: { state: "observed", parentPid: 1, processGroupId: 910 },
};
};
const signalGroup = vi.fn();
const host: ProcessOwnershipHost = {
platform: "darwin",
listProcesses: async () => [row],
processIdentities: async (members) =>
new Map(members.map(({ pid }) => [pid, identity()])),
runTokens: async (members) => {
tokenRead = true;
return new Map(
members.map(({ pid }) => [
pid,
{ state: "unavailable" as const, reason: "EACCES" },
]),
);
},
environment: async () => ({}),
signalGroup,
};
const result = await verifyNoTokenOwnedProcesses(
"run",
host,
[{ pid: row.pid, identity: "baseline-generation" }],
{
leaderPid: 100,
processGroupId: 100,
evidenceOwner: new ProcessOwnershipEvidenceOwner(),
},
);
if (lifetime === "absent" || lifetime === "zombie")
expect(result).toEqual({ cleaned: true, signaled: false });
else
expect(result).toMatchObject({
cleaned: false,
failures: [{ pid: row.pid }],
});
expect(signalGroup).not.toHaveBeenCalled();
},
);