mirror of
https://github.com/morluto/rea.git
synced 2026-11-07 05:56:19 -10:00
fix(kuna): recover overflow when project can answer (#8747)
* fix(kuna): recover overflow when project can answer * fix(kuna): provide actionable CLI overflow recovery * test(kuna): cover shared overflow cleanup * test(kuna): cover cancellation during overflow cleanup * fix(kuna): make overflow recovery usable after handoff
This commit is contained in:
1 parent
9196c30f6e
commit
1a9aa1296b
2 files changed
+170
-1
No files matched your search
@@ -1207,7 +1207,40 @@ export class KunaSessionClient implements AnalysisClient {
|
||||
options.signal,
|
||||
);
|
||||
if (outcome === EXPORT_READY) continue;
|
||||
if (!outcome.ok) return outcome;
|
||||
if (!outcome.ok) {
|
||||
const failure = outcome.error;
|
||||
if (
|
||||
failure instanceof AnalysisResourceConstraintError &&
|
||||
failure.resource === "memory" &&
|
||||
failure.reportedLimits?.output_limit_bytes ===
|
||||
KUNA_FUNCTION_TEXT_BYTES &&
|
||||
!failure.cleanupIncomplete &&
|
||||
this.#exportFailure === undefined &&
|
||||
this.#project !== undefined
|
||||
)
|
||||
return err(
|
||||
new AnalysisResourceConstraintError(
|
||||
operation,
|
||||
failure.resource,
|
||||
failure.reason,
|
||||
failure.reportedLimits,
|
||||
{
|
||||
cause: failure,
|
||||
capturedOutput: failure.capturedOutput,
|
||||
diagnostics: failure.diagnostics,
|
||||
partialObservation: failure.partialObservation,
|
||||
retryable: true,
|
||||
remediationAction:
|
||||
"The standalone output exceeds REA's fixed buffer. Run rea function for this target with Kuna to build the project, then retry this function. If the project cannot supply pseudocode, consider another provider.",
|
||||
continuingCallerGuidance: {
|
||||
remediationAction:
|
||||
"The standalone output exceeds REA's fixed buffer. If the original session is open, wait for its project export, for example by requesting procedure_assembly. Otherwise reopen the target and wait for its project. Then retry the function. If the project cannot supply pseudocode, consider another provider.",
|
||||
},
|
||||
},
|
||||
),
|
||||
);
|
||||
return outcome;
|
||||
}
|
||||
const exportFailure = await this.#settledExportFailure();
|
||||
return ok({
|
||||
result: outcome.value,
|
||||
|
||||
@@ -552,6 +552,90 @@ it("rejects oversized cached text before buffering while retaining its full expo
|
||||
}
|
||||
});
|
||||
|
||||
it("can recover a standalone output overflow from a completed project", async () => {
|
||||
const { open, kuna } = await session("rea-kuna-overflow-project-", {
|
||||
delayMs: 3000,
|
||||
decompileDelayMs: 200,
|
||||
decompilePadding: KUNA_FUNCTION_TEXT_BYTES + 1,
|
||||
});
|
||||
const client = open();
|
||||
const failures = await Promise.all(
|
||||
["main", "0x1000"].map((procedure) =>
|
||||
client.execute("procedure_pseudo_code", { procedure }),
|
||||
),
|
||||
);
|
||||
for (const failure of failures) {
|
||||
expect(failure.ok).toBe(false);
|
||||
if (failure.ok) continue;
|
||||
expect(projectAnalysisError(failure.error)).toMatchObject({
|
||||
code: "resource_constraint",
|
||||
retryable: true,
|
||||
remediation: { action: expect.stringContaining("reopen the target") },
|
||||
details: {
|
||||
reported_limits: { output_limit_bytes: KUNA_FUNCTION_TEXT_BYTES },
|
||||
},
|
||||
});
|
||||
const invocation = projectAnalysisError(failure.error, "invocation");
|
||||
expect(invocation.remediation.action).toContain("rea function");
|
||||
expect(invocation.remediation.action).not.toContain("procedure_assembly");
|
||||
}
|
||||
expect(
|
||||
(await fakeKunaCalls(kuna)).filter(({ argv }) => argv[0] === "decompile"),
|
||||
).toHaveLength(1);
|
||||
await run(client, "procedure_assembly", { procedure: "main" });
|
||||
const later = await run(client, "procedure_pseudo_code", {
|
||||
procedure: "main",
|
||||
});
|
||||
expect(later.rawResult).toMatchObject({ source: { kind: "project-export" } });
|
||||
await client.close();
|
||||
|
||||
const failed = await session("rea-kuna-overflow-failed-project-", {
|
||||
project: "exit-failure",
|
||||
decompilePadding: KUNA_FUNCTION_TEXT_BYTES + 1,
|
||||
});
|
||||
const failedClient = failed.open();
|
||||
expect(
|
||||
(await failedClient.execute("procedure_assembly", { procedure: "main" }))
|
||||
.ok,
|
||||
).toBe(false);
|
||||
const overflow = await failedClient.execute("procedure_pseudo_code", {
|
||||
procedure: "main",
|
||||
});
|
||||
expect(overflow.ok).toBe(false);
|
||||
if (!overflow.ok)
|
||||
expect(projectAnalysisError(overflow.error)).toMatchObject({
|
||||
code: "resource_constraint",
|
||||
retryable: false,
|
||||
remediation: { action: expect.stringContaining("Use another provider") },
|
||||
});
|
||||
await failedClient.close();
|
||||
|
||||
const unproven = unprovenCleanupLauncher("decompile");
|
||||
const retained = await session(
|
||||
"rea-kuna-overflow-retained-cleanup-",
|
||||
{ project: "hang", decompilePadding: KUNA_FUNCTION_TEXT_BYTES + 1 },
|
||||
{},
|
||||
unproven.launcher,
|
||||
);
|
||||
const retainedClient = retained.open();
|
||||
try {
|
||||
const result = await retainedClient.execute("procedure_pseudo_code", {
|
||||
procedure: "main",
|
||||
});
|
||||
expect(result.ok).toBe(false);
|
||||
if (!result.ok) {
|
||||
expect(result.error.cleanupIncomplete).toBe(true);
|
||||
expect(projectAnalysisError(result.error)).toMatchObject({
|
||||
code: "cleanup_incomplete",
|
||||
retryable: false,
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
unproven.release();
|
||||
expect(await retainedClient.close()).toEqual({ ok: true, value: null });
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps answering pseudocode one function at a time when the export fails", async () => {
|
||||
const { open } = await session("rea-kuna-export-failed-", {
|
||||
project: "exit-failure",
|
||||
@@ -585,6 +669,58 @@ it("keeps answering pseudocode one function at a time when the export fails", as
|
||||
await client.close();
|
||||
});
|
||||
|
||||
it("keeps an overflow as the primary failure when cancellation arrives during final cleanup", async () => {
|
||||
let enteredCleanup!: () => void;
|
||||
let releaseCleanup!: () => void;
|
||||
const entered = new Promise<void>((resolve) => (enteredCleanup = resolve));
|
||||
const released = new Promise<void>((resolve) => (releaseCleanup = resolve));
|
||||
const launcher: KunaProcessLauncher = async (options) => {
|
||||
const spawned = await spawnOwnedProviderProcess(options);
|
||||
if (options.arguments[0] !== "decompile") return spawned;
|
||||
return {
|
||||
...spawned,
|
||||
cleanup: async () => {
|
||||
const result = await cleanupOwnedProcessGroup(spawned.ownership);
|
||||
enteredCleanup();
|
||||
await released;
|
||||
return result;
|
||||
},
|
||||
};
|
||||
};
|
||||
const fixture = await session(
|
||||
"rea-kuna-overflow-cancel-finalization-",
|
||||
{ project: "hang", decompilePadding: KUNA_FUNCTION_TEXT_BYTES + 1 },
|
||||
{},
|
||||
launcher,
|
||||
);
|
||||
const client = fixture.open();
|
||||
const caller = new AbortController();
|
||||
const pending = client.execute(
|
||||
"procedure_pseudo_code",
|
||||
{ procedure: "main" },
|
||||
{ signal: caller.signal },
|
||||
);
|
||||
try {
|
||||
await entered;
|
||||
caller.abort("cancel during overflow cleanup");
|
||||
releaseCleanup();
|
||||
const result = await pending;
|
||||
expect(result.ok).toBe(false);
|
||||
if (!result.ok)
|
||||
expect(projectAnalysisError(result.error)).toMatchObject({
|
||||
code: "resource_constraint",
|
||||
details: {
|
||||
reported_limits: { output_limit_bytes: KUNA_FUNCTION_TEXT_BYTES },
|
||||
},
|
||||
});
|
||||
} finally {
|
||||
releaseCleanup();
|
||||
caller.abort();
|
||||
await pending;
|
||||
expect(await client.close()).toEqual({ ok: true, value: null });
|
||||
}
|
||||
});
|
||||
|
||||
it("stops waiting for the export when the caller cancels, without abandoning it", async () => {
|
||||
const { open } = await session("rea-kuna-wait-cancel-", { delayMs: 1500 });
|
||||
const client = open();
|
||||
|
||||
Reference in new issue
Block a user