mirror of
https://github.com/morluto/rea.git
synced 2026-10-31 03:45:39 +00:00
* fix(engines): keep format ceilings fixed and recover the EVMole deadline Promote the owned EVMole worker wall clock to timeout_ms and name it in recovery. Fixed CPU, byte, and address-space refusals in this theme say the adjustable deadline does not raise them. Refs #5910 Co-authored-by: morluto <morluto@users.noreply.github.com> * fix(engines): recover the allowance that refused the request Model capacity recovery follows a structured limit instead of message text, and the shared header ceiling no longer names Safetensors. Firmware signal errors keep captured tool output, oversized reports do not suggest an intermittent rerun, and NativeAOT hydration budget exhaustion explains the fixed parser allowance on the partial report. Co-authored-by: morluto <morluto@users.noreply.github.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: morluto <morluto@users.noreply.github.com>
343 lines
12 KiB
TypeScript
343 lines
12 KiB
TypeScript
import { lstat, type FileHandle } from "node:fs/promises";
|
|
import type { GpuCodeObjectsPort } from "../application/gpu/GpuCodeObjectsPort.js";
|
|
import {
|
|
createGpuCodeObjectsEvidence,
|
|
createGpuCodeObjectsPartialEvidence,
|
|
} from "../application/gpu/GpuCodeObjectsEvidence.js";
|
|
import type { ExecutionOptions } from "../application/AnalysisProvider.js";
|
|
import type {
|
|
GpuCodeObjects,
|
|
GpuCodeObjectsInput,
|
|
} from "../domain/gpu/gpuCodeObjects.js";
|
|
import {
|
|
gpuCodeObjectsSchema,
|
|
gpuCodeObjectsPartialSchema,
|
|
} from "../domain/gpu/gpuCodeObjects.js";
|
|
import { AnalysisError } from "../domain/analysisErrorBase.js";
|
|
import {
|
|
AnalysisArtifactChangedError,
|
|
AnalysisAccessDeniedError,
|
|
AnalysisCancelledError,
|
|
AnalysisResourceConstraintError,
|
|
AnalysisUnsupportedTargetError,
|
|
} from "../domain/analysisErrorCore.js";
|
|
import { BinaryTargetError } from "../domain/configurationErrors.js";
|
|
import { ProviderAdapterError } from "../domain/providerAdapterError.js";
|
|
import { err, ok, type Result } from "../domain/result.js";
|
|
import { ArtifactResourceScope } from "../artifacts/ArtifactResourceScope.js";
|
|
import { ArtifactReaderFailure } from "../artifacts/ArtifactReader.js";
|
|
import {
|
|
withArtifactRegularFile,
|
|
bindArtifactFileCleanup,
|
|
artifactFilePrimaryFailure,
|
|
artifactFileCancellationCause,
|
|
ArtifactFileCancelled,
|
|
ArtifactFileCleanupFailure,
|
|
} from "../artifacts/ArtifactFileOperation.js";
|
|
import { analysisErrorWithCleanupFailure } from "../domain/analysisErrorCleanup.js";
|
|
import {
|
|
BinaryFormatFailure,
|
|
BinaryResourceFailure,
|
|
} from "../native/metadata/BinaryContainer.js";
|
|
import {
|
|
GpuFileSource,
|
|
gpuCount,
|
|
GPU_OBJECT_BYTES,
|
|
GPU_REPORT_BYTES,
|
|
} from "./GpuSource.js";
|
|
import { GpuInventory } from "./GpuInventory.js";
|
|
import { ProviderCleanupError } from "../domain/providerCleanupError.js";
|
|
import { sameRegularFileState } from "../filesystem/RegularFile.js";
|
|
|
|
export const GPU_CODE_OBJECTS_PROVIDER_IDENTITY = {
|
|
id: "gpu-code-objects",
|
|
name: "REA static GPU code-object reader",
|
|
version: "1",
|
|
} as const;
|
|
const OPERATION = "inspect_gpu_code_objects";
|
|
|
|
/** One admitted descriptor and bounded report at a time; retained cleanup blocks new work. */
|
|
export class GpuCodeObjectsProvider implements GpuCodeObjectsPort {
|
|
readonly identity = GPU_CODE_OBJECTS_PROVIDER_IDENTITY;
|
|
readonly #resources = new ArtifactResourceScope();
|
|
#busy = false;
|
|
|
|
async close(): Promise<void> {
|
|
try {
|
|
await this.#resources.close();
|
|
} catch (cause) {
|
|
const error = new ProviderCleanupError(
|
|
this.identity.id,
|
|
this.#resources.cleanupResources,
|
|
{},
|
|
{ cause, operation: OPERATION },
|
|
);
|
|
bindArtifactFileCleanup(cause, error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async inspect(
|
|
input: GpuCodeObjectsInput,
|
|
options?: ExecutionOptions,
|
|
): Promise<Result<GpuCodeObjects, AnalysisError>> {
|
|
if (this.#busy)
|
|
return err(
|
|
new AnalysisResourceConstraintError(
|
|
OPERATION,
|
|
"capacity",
|
|
"One GPU inspection is already active",
|
|
{ concurrent_inspections: 1 },
|
|
{ retryable: true },
|
|
),
|
|
);
|
|
this.#busy = true;
|
|
let progress: { inventory: GpuInventory; size: number } | undefined;
|
|
try {
|
|
return ok(
|
|
await this.#resources.run(() =>
|
|
withArtifactRegularFile(
|
|
input.path,
|
|
this.#resources,
|
|
options?.signal,
|
|
(file) =>
|
|
this.#read(file, input, options?.signal, (value) => {
|
|
progress = value;
|
|
}),
|
|
),
|
|
),
|
|
);
|
|
} catch (cause) {
|
|
const primary = artifactFilePrimaryFailure(cause);
|
|
let error: AnalysisError;
|
|
if (primary instanceof AnalysisError) error = primary;
|
|
else if (primary instanceof BinaryResourceFailure)
|
|
error = new AnalysisResourceConstraintError(
|
|
OPERATION,
|
|
"memory",
|
|
primary.message,
|
|
{
|
|
maximum_object_bytes: GPU_OBJECT_BYTES,
|
|
maximum_report_bytes: GPU_REPORT_BYTES,
|
|
},
|
|
{
|
|
cause,
|
|
retryable: false,
|
|
remediationAction: `${primary.message} This release has no runtime override for that GPU byte allowance. The object allowance is ${String(GPU_OBJECT_BYTES)} bytes and the report allowance is ${String(GPU_REPORT_BYTES)} bytes. Additional host memory does not raise them. Repeating the unchanged request fails the same way.`,
|
|
},
|
|
);
|
|
else if (primary instanceof BinaryFormatFailure) {
|
|
error =
|
|
primary.kind === "unsupported"
|
|
? new AnalysisUnsupportedTargetError(
|
|
OPERATION,
|
|
input.path,
|
|
primary.message,
|
|
{ cause },
|
|
)
|
|
: new BinaryTargetError(input.path, primary.message, {
|
|
cause,
|
|
constraint: "file_format_rejected",
|
|
});
|
|
} else if (
|
|
primary instanceof ArtifactReaderFailure &&
|
|
primary.reason === "cancelled"
|
|
)
|
|
error = new AnalysisCancelledError(OPERATION, {
|
|
cause: artifactFileCancellationCause(primary),
|
|
});
|
|
else if (primary instanceof ArtifactFileCancelled)
|
|
error = new AnalysisCancelledError(OPERATION, {
|
|
cause: primary.fileCancellation.reason,
|
|
});
|
|
else if (
|
|
primary instanceof ArtifactReaderFailure &&
|
|
primary.reason === "integrity"
|
|
)
|
|
error = new AnalysisArtifactChangedError(
|
|
OPERATION,
|
|
input.path,
|
|
primary.message,
|
|
{ cause },
|
|
);
|
|
else if (
|
|
primary instanceof Error &&
|
|
"code" in primary &&
|
|
(primary.code === "EACCES" || primary.code === "EPERM")
|
|
)
|
|
error = new AnalysisAccessDeniedError(
|
|
OPERATION,
|
|
input.path,
|
|
primary.code,
|
|
{ cause: primary },
|
|
);
|
|
else if (
|
|
primary instanceof ArtifactReaderFailure &&
|
|
primary.reason === "limit"
|
|
)
|
|
error = new AnalysisResourceConstraintError(
|
|
OPERATION,
|
|
"memory",
|
|
primary.message,
|
|
{ maximum_object_bytes: GPU_OBJECT_BYTES },
|
|
{
|
|
cause: primary,
|
|
retryable: false,
|
|
remediationAction: `${primary.message} The GPU object allowance is ${String(GPU_OBJECT_BYTES)} bytes. This release has no runtime override. Additional host memory does not raise it. Repeating the unchanged request fails the same way.`,
|
|
},
|
|
);
|
|
else if (primary instanceof Error && "code" in primary)
|
|
error = new BinaryTargetError(input.path, primary.message, { cause });
|
|
else
|
|
error = new ProviderAdapterError(this.identity.id, OPERATION, {
|
|
cause,
|
|
diagnostics: {
|
|
path: input.path,
|
|
reason:
|
|
primary instanceof Error ? primary.message : String(primary),
|
|
},
|
|
});
|
|
if (
|
|
cause instanceof ArtifactReaderFailure &&
|
|
cause.cleanup !== undefined
|
|
) {
|
|
const cleanup = new ProviderCleanupError(
|
|
this.identity.id,
|
|
cause.cleanup.resources,
|
|
{ reason: cause.cleanup.reason },
|
|
{ cause, operation: OPERATION },
|
|
);
|
|
error = analysisErrorWithCleanupFailure(error, cleanup, OPERATION);
|
|
}
|
|
const completed =
|
|
cause instanceof ArtifactFileCleanupFailure &&
|
|
cause.fileCleanupFailure.outcome.kind === "completed"
|
|
? cause.fileCleanupFailure.outcome.value
|
|
: cause instanceof ArtifactFileCancelled
|
|
? cause.fileCancellation.outcome.value
|
|
: undefined;
|
|
if (completed !== undefined) {
|
|
// The descriptor owner retains observations even when its final close fails.
|
|
const report = gpuCodeObjectsSchema.safeParse(completed);
|
|
if (report.success)
|
|
error.retainPartialObservation(
|
|
createGpuCodeObjectsEvidence(report.data, this.identity, input),
|
|
);
|
|
}
|
|
if (
|
|
error.partialObservation === undefined &&
|
|
progress !== undefined &&
|
|
progress.inventory.objects.length > 0
|
|
) {
|
|
const partial = gpuCodeObjectsPartialSchema.parse({
|
|
kind: "gpu-code-objects-partial",
|
|
artifact: {
|
|
path: input.path,
|
|
observed_file_size: progress.size,
|
|
sha256: null,
|
|
file_identity: "unverified",
|
|
},
|
|
analysis: input.analysis,
|
|
objects: progress.inventory.objects,
|
|
kernels: progress.inventory.kernels,
|
|
ptx_artifacts: progress.inventory.ptx_artifacts,
|
|
coverage: { discovery: "partial", payloads: "partial" },
|
|
limitations: [
|
|
...progress.inventory.limitations,
|
|
"Only completed declarations observed before interruption are retained. Root file identity and completeness were not verified; positional IDs are scoped to this interrupted observation.",
|
|
],
|
|
});
|
|
error.retainPartialObservation(
|
|
createGpuCodeObjectsPartialEvidence(partial, this.identity, input),
|
|
);
|
|
}
|
|
bindArtifactFileCleanup(cause, error);
|
|
return err(error);
|
|
} finally {
|
|
this.#busy = false;
|
|
}
|
|
}
|
|
|
|
async #read(
|
|
file: FileHandle,
|
|
input: GpuCodeObjectsInput,
|
|
signal?: AbortSignal,
|
|
retainProgress?: (progress: {
|
|
inventory: GpuInventory;
|
|
size: number;
|
|
}) => void,
|
|
): Promise<GpuCodeObjects> {
|
|
const initial = await file.stat({ bigint: true });
|
|
const size = gpuCount(initial.size);
|
|
const source = new GpuFileSource(file, size, signal);
|
|
const sha256 = await source.digest();
|
|
if (input.expected_sha256 !== undefined && sha256 !== input.expected_sha256)
|
|
throw new AnalysisArtifactChangedError(
|
|
OPERATION,
|
|
input.path,
|
|
"GPU source does not match the inventory's selected SHA-256",
|
|
);
|
|
const inventory = new GpuInventory(input, signal);
|
|
retainProgress?.({ inventory, size });
|
|
const format = await inventory.inspect(source);
|
|
const after = await file.stat({ bigint: true });
|
|
const named = await lstat(input.path, { bigint: true }).catch(
|
|
(cause: unknown) => {
|
|
if (
|
|
cause instanceof Error &&
|
|
"code" in cause &&
|
|
["ENOENT", "ENOTDIR", "ELOOP"].includes(String(cause.code))
|
|
)
|
|
throw new ArtifactReaderFailure(
|
|
"integrity",
|
|
"Selected GPU path changed during inspection",
|
|
{ cause },
|
|
);
|
|
throw cause;
|
|
},
|
|
);
|
|
if ([after, named].some((state) => !sameRegularFileState(initial, state)))
|
|
throw new ArtifactReaderFailure(
|
|
"integrity",
|
|
"GPU source changed during inspection",
|
|
);
|
|
const leaves = inventory.objects.filter(
|
|
(row) =>
|
|
row.kind !== "cuda-fatbin" &&
|
|
row.kind !== "hip-bundle" &&
|
|
row.kind !== "native-section" &&
|
|
row.kind !== "host",
|
|
);
|
|
return gpuCodeObjectsSchema.parse({
|
|
artifact: {
|
|
path: input.path,
|
|
file_size: size,
|
|
sha256,
|
|
bytes_read: source.bytesRead,
|
|
},
|
|
format,
|
|
analysis: input.analysis,
|
|
objects: inventory.objects,
|
|
kernels: inventory.kernels,
|
|
ptx_artifacts: inventory.ptx_artifacts,
|
|
coverage: {
|
|
discovery_scope: "supported-section-names-and-standalone-formats",
|
|
discovery: inventory.discovery,
|
|
payloads:
|
|
input.analysis === "inventory"
|
|
? "not-requested"
|
|
: leaves.every((row) => row.inspection === "complete") &&
|
|
inventory.discovery === "complete"
|
|
? "complete"
|
|
: "partial",
|
|
examined_sections: inventory.examined_sections,
|
|
unknown_section_names: inventory.unknown_section_names,
|
|
},
|
|
limitations: [
|
|
...inventory.limitations,
|
|
"Full-file SHA-256 streams every file byte; section and selected payload reads add to artifact.bytes_read. Retained Evidence is historical and never reopens the target.",
|
|
],
|
|
});
|
|
}
|
|
}
|