mirror of
https://github.com/morluto/rea
synced 2026-10-28 06:46:47 -04:00
fix(setup): preserve cleanup ownership and resource reports (#6920)
* fix(setup): report host-owned cleanup resources alongside the primary's
SetupHost.close documents that it reports the resources a setup host still
owns, but its contract returned only a string. runSetup therefore built the
ProviderCleanupError from the primary failure's cleanupResources alone, so a
resource the host failed to release reached the projection only inside a
free-text reason while the error asks the caller to review the reported local
resources.
Return a SetupCloseReport instead. closeSetupResources absorbs the structured
resources of any cause that reports them, through
ArtifactReaderFailure.cleanupObservation, and leaves opaque failures
message-only so no resource name is invented. runSetup then unions the host's
resources with the primary's before building the typed cleanup error.
Two boundary tests read the close result directly: the skill-cleanup case now
asserts the retained backup path in the report reason rather than in a bare
string, and the shared ownership helper takes an opaque close result because it
only ever awaits undefined from either host it is given.
Validation: RED then green in src/application/Setup.recovery.test.ts, where
the new case previously received only the primary resource; 116 tests across
the seven Setup and cleanup-ownership suites pass; tsc --noEmit over the
changed set, its setup callers, and both boundary tests exits 0; oxlint
reports 0 errors; oxfmt --check is clean; verify:module-boundaries passes.
The two failures in src/setupEnvironment.test.ts reproduce identically on
pristine bded9c628 with this change stashed.
* fix(setup): preserve cleanup ownership through host reports
* fix(setup): bind finalization reports to cleanup owners
* fix(setup): retain live resources in returned cleanup views
* fix(setup): preserve completed cleanup and exact installer resources
* fix(setup): retain delegated cleanup across authority changes
* fix(setup): compose callback and source cleanup authorities
* fix(setup): retain producer cleanup failure reasons
* fix(cli): render structured setup cleanup resources
* fix(setup): separate failure evidence from cleanup ownership
* fix(setup): normalize close failures and unify lease binding
* fix(setup): preserve direct lease resource attribution
---------
Co-authored-by: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
This commit is contained in:
@@ -23,7 +23,9 @@ it.each(["before release", "after release"])(
|
||||
if (binding === "before release") lease.bindCleanupError(error);
|
||||
await lease.release();
|
||||
if (binding === "after release") lease.bindCleanupError(error);
|
||||
expect(error.hasCleanupOwner).toBe(true);
|
||||
expect(error.cleanupIncomplete).toBe(false);
|
||||
expect(error.cleanupResources).toEqual(["owned resource"]);
|
||||
expect(error.cause).toBe(cause);
|
||||
await lease.release();
|
||||
expect(releases).toBe(1);
|
||||
@@ -178,3 +180,24 @@ it("shares an in-flight release and permits one new attempt after failure", asyn
|
||||
for (const settle of pending) settle();
|
||||
}
|
||||
});
|
||||
|
||||
it("preserves a producer's live attribution when binding direct completion", async () => {
|
||||
const error = new ProviderCleanupError("fixture", ["initial resource"], {});
|
||||
let resources = ["first resource", "second resource"];
|
||||
error.bindCleanupResourceOwner({
|
||||
get cleanupResources() {
|
||||
return resources;
|
||||
},
|
||||
});
|
||||
const lease = new AnalysisCleanupLease(async () => {
|
||||
resources = [];
|
||||
});
|
||||
lease.bindCleanupError(error);
|
||||
expect(error.hasCleanupOwner).toBe(true);
|
||||
expect(error.cleanupResources).toEqual(["first resource", "second resource"]);
|
||||
resources = ["second resource"];
|
||||
expect(error.cleanupResources).toEqual(["second resource"]);
|
||||
await lease.release();
|
||||
expect(error.cleanupIncomplete).toBe(false);
|
||||
expect(error.cleanupResources).toEqual([]);
|
||||
});
|
||||
|
||||
@@ -7,7 +7,6 @@ import type {
|
||||
export class AnalysisCleanupLease {
|
||||
#released = false;
|
||||
#releasePromise: Promise<void> | undefined;
|
||||
#errors: WeakRef<AnalysisError>[] = [];
|
||||
readonly #groups = new Set<AnalysisCleanupLeaseGroup>();
|
||||
|
||||
/** Whether this exact owner has confirmed release. */
|
||||
@@ -64,7 +63,6 @@ export class AnalysisCleanupLease {
|
||||
.then(async () => {
|
||||
await this.cleanup();
|
||||
this.#released = true;
|
||||
this.#resolveBoundErrors();
|
||||
for (const group of this.#groups) group.ownerReleased(this);
|
||||
this.#groups.clear();
|
||||
})
|
||||
@@ -77,14 +75,9 @@ export class AnalysisCleanupLease {
|
||||
|
||||
/** Binding can follow a concurrent retry that already confirmed release. */
|
||||
bindCleanupError(error: AnalysisError): void {
|
||||
if (this.#released) {
|
||||
error.resolveCleanup();
|
||||
return;
|
||||
}
|
||||
this.#errors = [
|
||||
...this.#errors.filter((reference) => reference.deref() !== undefined),
|
||||
new WeakRef(error),
|
||||
];
|
||||
new AnalysisCleanupLeaseGroup(error, [this], {
|
||||
preserveResourceAttribution: true,
|
||||
});
|
||||
}
|
||||
|
||||
/** Bind one aggregate error to every exact lease in a failed retry set. */
|
||||
@@ -97,11 +90,6 @@ export class AnalysisCleanupLease {
|
||||
if (!retained.hasLiveError) this.#groups.delete(retained);
|
||||
this.#groups.add(group);
|
||||
}
|
||||
|
||||
#resolveBoundErrors(): void {
|
||||
for (const reference of this.#errors) reference.deref()?.resolveCleanup();
|
||||
this.#errors = [];
|
||||
}
|
||||
}
|
||||
|
||||
/** Conjunctive cleanup binding for an error covering multiple exact owners. */
|
||||
@@ -109,11 +97,17 @@ export class AnalysisCleanupLeaseGroup {
|
||||
readonly #error: WeakRef<AnalysisError>;
|
||||
readonly #pending: Set<AnalysisCleanupLease>;
|
||||
|
||||
constructor(error: AnalysisError, owners: readonly AnalysisCleanupLease[]) {
|
||||
constructor(
|
||||
error: AnalysisError,
|
||||
owners: readonly AnalysisCleanupLease[],
|
||||
options: { readonly preserveResourceAttribution?: boolean } = {},
|
||||
) {
|
||||
this.#error = new WeakRef(error);
|
||||
this.#pending = new Set(owners);
|
||||
if (this.#pending.size === 0) return;
|
||||
error.bindCleanupResourceOwner(this);
|
||||
if (options.preserveResourceAttribution === true)
|
||||
error.bindCleanupCompletionOwner(this);
|
||||
else error.bindCleanupResourceOwner(this);
|
||||
for (const owner of this.#pending) owner.bindCleanupGroup(this);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user