mirror of
https://github.com/morluto/rea.git
synced 2026-10-22 22:02:27 -05:00
* refactor(domain): consolidate canonical ordering and digest helpers (#624) * refactor(domain): consolidate canonical ordering and digest helpers Single owner for code-point ordering (domain/canonicalOrdering.ts) and canonical JSON digests (domain/comparisonSemantics.ts). Delete all local compareCodePoints/compareText/digestCanonical duplicates and migrate every importer. No re-export shims. * fix(ci): allow slower real browser startup * chore(config): remove dead eslint, dedupe scripts, cacheable turbo tasks Delete eslint.config.js (oxlint is the linter), drop lint:dead alias, pin smol-toml, fold jscpd/scan:todos into check:pr, make test:run cacheable, align oxlint ignores. * fix(config): hash all test inputs for cached runs * docs: fix architecture diagram and pretty-print tool catalog Drop dangling REPLAY edges, add dotnet/reference/error/evaluation nodes. Emit 2-space JSON from the catalog generator and regenerate. * test: extract shared fixtures, drop knip carve-outs Dedupe android/apple session tests, bundler adapter headers, and reconstruction helpers into tests/support fixtures. Delete unused type aliases and the knip ignoreIssues block. Lower jscpd threshold 2.7 to 2.5. * refactor(app): split Setup and ProcessOwnership modules Extract SetupTypes/SetupHost from Setup.ts and move observation helpers into ProcessOwnershipObservation/ProcessTree. Migrate all importers including CLI commands. No re-export shims.
212 lines
5.9 KiB
TypeScript
212 lines
5.9 KiB
TypeScript
import { describe, expect, it, vi } from "vitest";
|
|
import {
|
|
cleanupOwnedProcessGroup,
|
|
verifyNoTokenOwnedProcesses,
|
|
type ProcessOwnershipHost,
|
|
} from "./ProcessOwnership.js";
|
|
import { observeOwnedProcessLineage } from "./ProcessOwnershipObservation.js";
|
|
const ownership = {
|
|
runId: "run-token",
|
|
leaderPid: 100,
|
|
processGroupId: 100,
|
|
};
|
|
const host = (
|
|
environments: Readonly<Record<number, Readonly<Record<string, string>>>>,
|
|
): {
|
|
readonly adapter: ProcessOwnershipHost;
|
|
readonly signalGroup: ReturnType<typeof vi.fn>;
|
|
} => {
|
|
const signalGroup = vi.fn();
|
|
return {
|
|
adapter: {
|
|
listProcesses: () =>
|
|
Promise.resolve(
|
|
Object.keys(environments).map((pid) => ({
|
|
pid: Number(pid),
|
|
parentPid: Number(pid) === 100 ? 1 : 100,
|
|
processGroupId: 100,
|
|
state: "S",
|
|
command: "fixture",
|
|
})),
|
|
),
|
|
environment: (pid) => Promise.resolve(environments[pid] ?? {}),
|
|
signalGroup,
|
|
},
|
|
signalGroup,
|
|
};
|
|
};
|
|
describe("owned process-group cleanup discovery", () => {
|
|
it("signals token-owned groups that were reparented outside the launcher tree", async () => {
|
|
const signalGroup = vi.fn();
|
|
const adapter: ProcessOwnershipHost = {
|
|
listProcesses: () =>
|
|
Promise.resolve([
|
|
{
|
|
pid: 100,
|
|
parentPid: 1,
|
|
processGroupId: 100,
|
|
state: "S",
|
|
command: "fixture",
|
|
},
|
|
{
|
|
pid: 900,
|
|
parentPid: 1,
|
|
processGroupId: 900,
|
|
state: "S",
|
|
command: "detached-helper",
|
|
},
|
|
{
|
|
pid: 901,
|
|
parentPid: 900,
|
|
processGroupId: 900,
|
|
state: "S",
|
|
command: "detached-worker",
|
|
},
|
|
]),
|
|
environment: () => Promise.resolve({ REA_PROCESS_RUN_ID: "run-token" }),
|
|
signalGroup,
|
|
};
|
|
|
|
await expect(
|
|
cleanupOwnedProcessGroup(
|
|
{ ...ownership, sweepTokenOwnedProcesses: true },
|
|
adapter,
|
|
),
|
|
).resolves.toEqual({ cleaned: true, signaled: true });
|
|
expect(signalGroup.mock.calls.map(([groupId]) => groupId)).toEqual([
|
|
100, 900,
|
|
]);
|
|
});
|
|
|
|
it("fails closed when a token-owned process remains after cleanup", async () => {
|
|
const adapter: ProcessOwnershipHost = {
|
|
listProcesses: () =>
|
|
Promise.resolve([
|
|
{
|
|
pid: 900,
|
|
parentPid: 1,
|
|
processGroupId: 900,
|
|
state: "S",
|
|
command: "detached-helper",
|
|
},
|
|
]),
|
|
environment: () => Promise.resolve({ REA_PROCESS_RUN_ID: "run-token" }),
|
|
signalGroup: vi.fn(),
|
|
};
|
|
|
|
await expect(
|
|
verifyNoTokenOwnedProcesses("run-token", adapter),
|
|
).resolves.toEqual({
|
|
cleaned: false,
|
|
reason: "token-owned process remained after cleanup",
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("owned process-group cleanup discovery", () => {
|
|
it("fails before signaling when any descendant-group member is unowned", async () => {
|
|
const signalGroup = vi.fn();
|
|
const adapter: ProcessOwnershipHost = {
|
|
listProcesses: () =>
|
|
Promise.resolve([
|
|
{
|
|
pid: 100,
|
|
parentPid: 1,
|
|
processGroupId: 100,
|
|
state: "S",
|
|
command: "fixture",
|
|
},
|
|
{
|
|
pid: 101,
|
|
parentPid: 100,
|
|
processGroupId: 101,
|
|
state: "S",
|
|
command: "child-session",
|
|
},
|
|
{
|
|
pid: 104,
|
|
parentPid: 101,
|
|
processGroupId: 101,
|
|
state: "S",
|
|
command: "child-helper",
|
|
},
|
|
]),
|
|
environment: (pid) =>
|
|
Promise.resolve({
|
|
REA_PROCESS_RUN_ID: pid === 104 ? "other-run" : "run-token",
|
|
}),
|
|
signalGroup,
|
|
};
|
|
await expect(cleanupOwnedProcessGroup(ownership, adapter)).resolves.toEqual(
|
|
{
|
|
cleaned: false,
|
|
reason: "process tree contains an unowned or PID-reused process",
|
|
failures: [{ pid: 104, reason: "run-token-mismatch" }],
|
|
},
|
|
);
|
|
expect(signalGroup).not.toHaveBeenCalled();
|
|
});
|
|
it("fails before signaling when a descendant group leader is not rooted", async () => {
|
|
const signalGroup = vi.fn();
|
|
const adapter: ProcessOwnershipHost = {
|
|
listProcesses: () =>
|
|
Promise.resolve([
|
|
{
|
|
pid: 100,
|
|
parentPid: 1,
|
|
processGroupId: 100,
|
|
state: "S",
|
|
command: "fixture",
|
|
},
|
|
{
|
|
pid: 105,
|
|
parentPid: 100,
|
|
processGroupId: 101,
|
|
state: "S",
|
|
command: "descendant",
|
|
},
|
|
{
|
|
pid: 101,
|
|
parentPid: 999,
|
|
processGroupId: 101,
|
|
state: "S",
|
|
command: "unrelated-group-leader",
|
|
},
|
|
]),
|
|
environment: () => Promise.resolve({ REA_PROCESS_RUN_ID: "run-token" }),
|
|
signalGroup,
|
|
};
|
|
await expect(cleanupOwnedProcessGroup(ownership, adapter)).resolves.toEqual(
|
|
{
|
|
cleaned: false,
|
|
reason:
|
|
"descendant process-group leader identity could not be verified",
|
|
},
|
|
);
|
|
expect(signalGroup).not.toHaveBeenCalled();
|
|
});
|
|
it("records sorted launcher and descendant lineage after token checks", async () => {
|
|
const { adapter } = host({
|
|
102: { REA_PROCESS_RUN_ID: "run-token" },
|
|
100: { REA_PROCESS_RUN_ID: "run-token" },
|
|
101: { REA_PROCESS_RUN_ID: "run-token" },
|
|
});
|
|
await expect(
|
|
observeOwnedProcessLineage(ownership, adapter),
|
|
).resolves.toEqual({
|
|
status: "verified",
|
|
observedAt: expect.any(String),
|
|
lineage: {
|
|
runId: "run-token",
|
|
launcherPid: 100,
|
|
launcherParentPid: 1,
|
|
processGroupId: 100,
|
|
descendants: [
|
|
{ pid: 101, parentPid: 100, processGroupId: 100 },
|
|
{ pid: 102, parentPid: 100, processGroupId: 100 },
|
|
],
|
|
},
|
|
});
|
|
});
|
|
});
|