mirror of
https://github.com/morluto/rea
synced 2026-10-27 06:33:32 -04:00
fix(session): own target admission descriptors through finalization
This commit is contained in:
@@ -53,6 +53,10 @@ import { ProviderAdapterError } from "../../domain/providerAdapterError.js";
|
||||
import { profilelessNativeDispatchProfile } from "./ProfilelessNativeWorkflowSnapshot.js";
|
||||
import type { AnalysisSnapshot } from "../../domain/analysisSnapshot.js";
|
||||
import { AnalysisSnapshotMismatchError } from "../../domain/evidenceErrors.js";
|
||||
import {
|
||||
parseBinaryTarget,
|
||||
type BinaryTargetResolverOptions,
|
||||
} from "../BinaryTargetResolver.js";
|
||||
import {
|
||||
resolveAppBundleResourceRoot,
|
||||
type ResolvedAppBundleResourceRoot,
|
||||
@@ -422,6 +426,17 @@ export class BinarySession
|
||||
return () => this.#availabilityListeners.delete(listener);
|
||||
}
|
||||
|
||||
/** Resolve target identity under this session's file cleanup owner. */
|
||||
resolveTarget(
|
||||
path: string,
|
||||
options: Omit<BinaryTargetResolverOptions, "resources"> = {},
|
||||
) {
|
||||
return parseBinaryTarget(path, {
|
||||
...options,
|
||||
resources: this.fileResources,
|
||||
});
|
||||
}
|
||||
|
||||
/** Resolve a target and provider profile without creating a provider client. */
|
||||
previewTarget(
|
||||
target: BinaryTarget,
|
||||
@@ -464,6 +479,7 @@ export class BinarySession
|
||||
return this.#open(
|
||||
(admitted) =>
|
||||
resolveSessionOpen({
|
||||
resources: this.fileResources,
|
||||
router: this.#providerRouter,
|
||||
current: this.#active,
|
||||
path,
|
||||
@@ -850,7 +866,7 @@ export class BinarySession
|
||||
});
|
||||
if (clearRecords) this.clearSessionRecords();
|
||||
this.#clearRuntimeAvailability();
|
||||
const fileResources = await this.closeWriteResources();
|
||||
const fileResources = await this.closeFileResources();
|
||||
if (!fileResources.ok) onCleanupFailure?.(fileResources.error);
|
||||
const candidateResources = await this.#providerRouter.closeOwnedResources();
|
||||
if (!candidateResources.ok) onCleanupFailure?.(candidateResources.error);
|
||||
@@ -865,20 +881,24 @@ export class BinarySession
|
||||
];
|
||||
let resourceFailure = resourceFailures[0];
|
||||
for (const next of resourceFailures.slice(1))
|
||||
if (resourceFailure !== undefined)
|
||||
if (resourceFailure !== undefined) {
|
||||
const previous = resourceFailure;
|
||||
resourceFailure = analysisErrorWithCleanupFailure(
|
||||
resourceFailure,
|
||||
previous,
|
||||
next,
|
||||
"close_binary",
|
||||
);
|
||||
if (!closed.ok && resourceFailure !== undefined)
|
||||
return err(
|
||||
analysisErrorWithCleanupFailure(
|
||||
closed.error,
|
||||
resourceFailure,
|
||||
"close_binary",
|
||||
),
|
||||
resourceFailure.bindCleanupDependencies([previous, next]);
|
||||
}
|
||||
if (!closed.ok && resourceFailure !== undefined) {
|
||||
const combined = analysisErrorWithCleanupFailure(
|
||||
closed.error,
|
||||
resourceFailure,
|
||||
"close_binary",
|
||||
);
|
||||
combined.bindCleanupDependencies([closed.error, resourceFailure]);
|
||||
return err(combined);
|
||||
}
|
||||
if (!closed.ok) return closed;
|
||||
return resourceFailure === undefined ? ok(null) : err(resourceFailure);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
} from "../../domain/analysisSnapshot.js";
|
||||
import type { AnalysisSnapshot } from "../../domain/analysisSnapshot.js";
|
||||
import { parseBinaryTarget } from "../BinaryTargetResolver.js";
|
||||
import type { ArtifactResourceScope } from "../../artifacts/ArtifactResourceScope.js";
|
||||
import type { BinaryTarget } from "../../domain/binaryTargetTypes.js";
|
||||
import {
|
||||
EvidenceIntegrityError,
|
||||
@@ -56,6 +57,7 @@ export interface ResolvedSessionOpen {
|
||||
}
|
||||
|
||||
interface ResolveSessionOpenInput {
|
||||
readonly resources: ArtifactResourceScope;
|
||||
readonly router: SessionProviderRouter;
|
||||
readonly current: CurrentOpenBinding | undefined;
|
||||
readonly path: string;
|
||||
@@ -83,6 +85,7 @@ export const resolveSessionOpen = async (
|
||||
): Promise<Result<ResolvedSessionOpen, AnalysisError>> => {
|
||||
const { path, options } = input;
|
||||
const parsed = await parseBinaryTarget(path, {
|
||||
resources: input.resources,
|
||||
...(options.targetKind === undefined
|
||||
? {}
|
||||
: { targetKind: options.targetKind }),
|
||||
|
||||
@@ -33,7 +33,12 @@ import { AnalysisSnapshotCache } from "./AnalysisSnapshotCache.js";
|
||||
import { InvestigationRecords } from "../investigation/InvestigationRecords.js";
|
||||
import { ArtifactResourceScope } from "../../artifacts/ArtifactResourceScope.js";
|
||||
import { ArtifactReaderFailure } from "../../artifacts/ArtifactReader.js";
|
||||
import { writeAnalysisSnapshot as writeSnapshotFile } from "./AnalysisSnapshotFiles.js";
|
||||
import { bindRegularFileCleanup } from "../RegularFileRead.js";
|
||||
import {
|
||||
writeAnalysisSnapshot as writeSnapshotFile,
|
||||
readAnalysisSnapshot as readSnapshotFile,
|
||||
} from "./AnalysisSnapshotFiles.js";
|
||||
import { ProviderCleanupError } from "../../domain/providerCleanupError.js";
|
||||
import { workflowAnalysisProfile } from "../WorkflowAnalysisProfile.js";
|
||||
import {
|
||||
hasOnlyProfilelessNativeDispatchEntries,
|
||||
@@ -63,10 +68,10 @@ export interface WorkflowSnapshotRecordInput {
|
||||
/** Own binary snapshots and coordinate the composed investigation records. */
|
||||
export abstract class BinarySessionRecords {
|
||||
readonly #records: InvestigationRecords;
|
||||
readonly #writeResources = new ArtifactResourceScope();
|
||||
protected readonly fileResources = new ArtifactResourceScope();
|
||||
|
||||
protected get hasPendingFileResources(): boolean {
|
||||
return this.#writeResources.hasPending;
|
||||
return this.fileResources.hasPending;
|
||||
}
|
||||
readonly #snapshot = new AnalysisSnapshotCache();
|
||||
#snapshotInvalidated = false;
|
||||
@@ -112,6 +117,11 @@ export abstract class BinarySessionRecords {
|
||||
return this.#records.immutableEvidenceBundle();
|
||||
}
|
||||
|
||||
/** Read one snapshot while retaining failed descriptors under this session. */
|
||||
readAnalysisSnapshot(path: string, signal?: AbortSignal) {
|
||||
return readSnapshotFile(path, signal, this.fileResources);
|
||||
}
|
||||
|
||||
/** Persist one provider-neutral snapshot under this session's resource owner. */
|
||||
writeAnalysisSnapshot(
|
||||
snapshot: AnalysisSnapshot,
|
||||
@@ -120,28 +130,27 @@ export abstract class BinarySessionRecords {
|
||||
): Promise<
|
||||
Result<{ readonly path: string; readonly bytes: number }, AnalysisError>
|
||||
> {
|
||||
return writeSnapshotFile(snapshot, path, overwrite, this.#writeResources);
|
||||
return writeSnapshotFile(snapshot, path, overwrite, this.fileResources);
|
||||
}
|
||||
|
||||
/** Retry and release staged snapshot files at the owning session shutdown. */
|
||||
async closeWriteResources(): Promise<Result<null, AnalysisError>> {
|
||||
/** Retry retained target reads and snapshot files at the owning session shutdown. */
|
||||
async closeFileResources(): Promise<Result<null, AnalysisError>> {
|
||||
try {
|
||||
await this.#writeResources.retryCleanup();
|
||||
await this.fileResources.retryCleanup();
|
||||
return ok(null);
|
||||
} catch (cause: unknown) {
|
||||
const observation = ArtifactReaderFailure.cleanupObservation(
|
||||
cause,
|
||||
"analysis snapshot staging",
|
||||
"analysis session files",
|
||||
);
|
||||
return err(
|
||||
new EvidenceFileError("write", "io", {
|
||||
cause,
|
||||
cleanup: {
|
||||
reason: observation.reason,
|
||||
resources: observation.resources,
|
||||
},
|
||||
}),
|
||||
const error = new ProviderCleanupError(
|
||||
"rea",
|
||||
observation.resources,
|
||||
{ reason: observation.reason },
|
||||
{ cause, operation: "close_binary" },
|
||||
);
|
||||
bindRegularFileCleanup(cause, error);
|
||||
return err(error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user