diff --git a/src/lib/actions/sandbox/connect-flow.test.ts b/src/lib/actions/sandbox/connect-flow.test.ts index 376ab383747..a298408bb01 100644 --- a/src/lib/actions/sandbox/connect-flow.test.ts +++ b/src/lib/actions/sandbox/connect-flow.test.ts @@ -609,7 +609,7 @@ describe("connectSandbox flow", () => { ); }); - it("probe-only reports failure to create new authority after secure absence is proven (#8942)", async () => { + it("probe-only completes recovery after secure absence is proven and reports unpublished evidence (#9280)", async () => { const harness = createConnectHarness({ readinessDecision: { kind: "fallback", @@ -620,18 +620,21 @@ describe("connectSandbox flow", () => { fenceFailed: true, recoveryBlocked: false, }, + readinessPublicationResult: { kind: "evidence-failed" }, }); await expect(harness.connectSandbox("alpha", { probeOnly: true })).rejects.toThrow( "process.exit(1)", ); - expect(harness.checkAndRecoverSpy).not.toHaveBeenCalled(); - expect(harness.errorSpy.mock.calls.flat().join("\n")).toContain( - "no prior launch-readiness evidence can be accepted, but new launch-readiness authority could not be created", + expect(harness.checkAndRecoverSpy).toHaveBeenCalledOnce(); + expect(harness.ensureLiveSandboxSpy).toHaveBeenCalled(); + expect(harness.publishLaunchReadinessSpy).toHaveBeenCalledOnce(); + expect(harness.errorSpy).toHaveBeenCalledWith( + " Probe failed: complete probe and recovery succeeded, but final launch-readiness evidence could not be verified or published.", ); expect(harness.errorSpy.mock.calls.flat().join("\n")).not.toContain( - "prior launch-readiness evidence could not be fenced", + "new launch-readiness authority could not be created", ); }); diff --git a/src/lib/actions/sandbox/connect.ts b/src/lib/actions/sandbox/connect.ts index 8692356a3a9..ab707c81e3f 100644 --- a/src/lib/actions/sandbox/connect.ts +++ b/src/lib/actions/sandbox/connect.ts @@ -1285,11 +1285,19 @@ export async function connectSandbox( console.log(` Probe complete: launch readiness is healthy for '${sandboxName}'.`); return; } - if (readiness.fenceFailed && readiness.authorityUnsupported !== true) { + // Refuse recovery only when a prior epoch might exist and could not be + // durably rotated. When the authority and receipt are both securely + // absent but new authority creation fails (fenceFailed without + // recoveryBlocked), the documented contract runs the complete preflight + // and recovery and reports the publication failure afterwards, exactly + // as `launch` does for the same decision (#9280). + if ( + readiness.fenceFailed && + readiness.authorityUnsupported !== true && + readiness.recoveryBlocked + ) { console.error( - readiness.recoveryBlocked - ? " Probe failed: complete probe and recovery did not run because prior launch-readiness evidence could not be fenced. Repair the current user's secure OS runtime authority and NemoClaw state permissions, then retry." - : " Probe failed: no prior launch-readiness evidence can be accepted, but new launch-readiness authority could not be created. Repair the current user's secure OS runtime authority and NemoClaw state permissions, then retry.", + " Probe failed: complete probe and recovery did not run because prior launch-readiness evidence could not be fenced. Repair the current user's secure OS runtime authority and NemoClaw state permissions, then retry.", ); process.exit(1); }