mirror of
https://github.com/morluto/rea.git
synced 2026-11-06 23:06:18 +00:00
fix(session): report a missing Linux ps as provider unavailability in status (#7180)
Keep binary_session available when Linux owned-child probes lack procps ps. Preserve typed ownership-prerequisite failures in direct execution and report per-provider readiness in status. Independently validated on latest main through real stdio MCP plus focused regression, static and documentation checks. Closes #7164.
This commit is contained in:
10 files changed
+298
-21
No files matched your search
@@ -16,10 +16,12 @@ import type { BinaryTarget } from "../../domain/binaryTargetTypes.js";
|
||||
import { ProviderAdapterError } from "../../domain/providerAdapterError.js";
|
||||
import { ProviderCleanupError } from "../../domain/providerCleanupError.js";
|
||||
import {
|
||||
AnalysisCapabilityUnavailableError,
|
||||
AnalysisUnsupportedTargetError,
|
||||
AnalysisCancelledError,
|
||||
} from "../../domain/analysisErrorCore.js";
|
||||
import { ProviderSelectionError } from "../../domain/providerSelectionError.js";
|
||||
import { LinuxOwnedProcessPrerequisiteError } from "../../process/ProcessOwnershipObservation.js";
|
||||
import { projectAnalysisError } from "../../domain/analysisErrorProjection.js";
|
||||
import { err, ok } from "../../domain/result.js";
|
||||
|
||||
@@ -109,6 +111,55 @@ it("uses one owner readiness snapshot for candidate availability and capabilitie
|
||||
});
|
||||
});
|
||||
|
||||
it("reports a missing Linux ps as one candidate's unavailability and keeps the others", async () => {
|
||||
const base = candidate("alpha").provider;
|
||||
const gamma = candidate("gamma").provider;
|
||||
const refused: AnalysisProviderCandidate = {
|
||||
...base,
|
||||
// Mirrors a provider adapter re-attributing the refusal to its own operation.
|
||||
inspectReadinessSnapshot: () => {
|
||||
throw new AnalysisCapabilityUnavailableError(
|
||||
"process-ownership",
|
||||
"ghidra:inspection",
|
||||
"ps is unavailable",
|
||||
{
|
||||
cause: new LinuxOwnedProcessPrerequisiteError(
|
||||
new Error("spawn ps ENOENT"),
|
||||
),
|
||||
},
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
const statuses = await new AnalysisProviderRegistry([
|
||||
refused,
|
||||
gamma,
|
||||
]).candidates();
|
||||
expect(statuses).toMatchObject([
|
||||
{
|
||||
provider: { id: "alpha" },
|
||||
availability: {
|
||||
status: "unavailable",
|
||||
code: "runtime_unavailable",
|
||||
reason: expect.stringContaining("spawn ps ENOENT") as unknown,
|
||||
diagnostics: { stage: "ownership_preparation" },
|
||||
},
|
||||
capabilities: [{ operation: "address_name" }],
|
||||
},
|
||||
{ provider: { id: "gamma" }, availability: { status: "available" } },
|
||||
]);
|
||||
|
||||
const selected = await new AnalysisProviderRegistry([refused]).select(
|
||||
DATABASE_TARGET,
|
||||
"alpha",
|
||||
);
|
||||
if (selected.ok) throw new Error("expected the prerequisite refusal");
|
||||
expect(selected.error).toMatchObject({
|
||||
_tag: "AnalysisCapabilityUnavailableError",
|
||||
providerId: "process-ownership",
|
||||
});
|
||||
});
|
||||
|
||||
it("selects the sole available provider and honors explicit selection precedence", async () => {
|
||||
const alpha = candidate("alpha");
|
||||
const beta = candidate("beta", { available: false });
|
||||
|
||||
@@ -35,6 +35,7 @@ import type {
|
||||
AnalysisProviderCandidate,
|
||||
AnalysisProviderBoundClientFactory,
|
||||
AnalysisProviderInspectionOptions,
|
||||
AnalysisProviderReadinessSnapshot,
|
||||
CapabilityDescriptor,
|
||||
ProviderAvailability,
|
||||
ProviderIdentity,
|
||||
@@ -46,6 +47,7 @@ import {
|
||||
type AnalysisProviderCandidateEvaluation,
|
||||
} from "./AnalysisProviderEvaluation.js";
|
||||
import { compareUnicodeCodePoints } from "../../domain/unicodeCodePointOrder.js";
|
||||
import { ownedProcessPrerequisiteAvailability } from "../ownedProcessAvailability.js";
|
||||
|
||||
/** Deterministic discovery truth for one configured deep provider. */
|
||||
export interface AnalysisProviderCandidateStatus {
|
||||
@@ -733,21 +735,32 @@ const candidateStatus = async (
|
||||
const { signal, scope, resourceScope, operation } = context;
|
||||
if (signal.aborted)
|
||||
throw new AnalysisCancelledError(operation, { cause: signal.reason });
|
||||
const readiness =
|
||||
provider.inspectReadinessSnapshot === undefined
|
||||
? {
|
||||
availability: await provider.inspectAvailability({
|
||||
let readiness: AnalysisProviderReadinessSnapshot;
|
||||
try {
|
||||
readiness =
|
||||
provider.inspectReadinessSnapshot === undefined
|
||||
? {
|
||||
availability: await provider.inspectAvailability({
|
||||
signal,
|
||||
scope,
|
||||
resourceScope,
|
||||
}),
|
||||
capabilities: provider.capabilities(),
|
||||
}
|
||||
: await provider.inspectReadinessSnapshot({
|
||||
signal,
|
||||
scope,
|
||||
resourceScope,
|
||||
}),
|
||||
capabilities: provider.capabilities(),
|
||||
}
|
||||
: await provider.inspectReadinessSnapshot({
|
||||
signal,
|
||||
scope,
|
||||
resourceScope,
|
||||
});
|
||||
});
|
||||
} catch (cause: unknown) {
|
||||
// Status reports the gap per provider; execution keeps the prerequisite's owner.
|
||||
const availability =
|
||||
operation === "binary_session"
|
||||
? ownedProcessPrerequisiteAvailability(cause)
|
||||
: undefined;
|
||||
if (availability === undefined) throw cause;
|
||||
readiness = { availability, capabilities: provider.capabilities() };
|
||||
}
|
||||
return await projectCandidateStatus(provider, target, readiness, {
|
||||
signal,
|
||||
scope,
|
||||
|
||||
Reference in new issue
Block a user