| author | |
| committer | |
| log | 7722cb6d2e44e0aeb136a86bc2165f1cc733146a |
| tree | 9f7c5c665666f31078ecf37a24eea790ec746b8d |
| parent | c9a59a26bb85902b9b2e828e1f99cc0760105ae4 |
- make integration harness dispose path always remove temp root dirs via `Effect.ensuring`
- preserve shutdown failure by surfacing the first teardown error cause
- add integration test that forces `providerService.stopAll` failure and verifies temp dir cleanup2 files changed, 48 insertions(+), 6 deletions(-)
apps/server/integration/OrchestrationEngineHarness.integration.ts+29-5| ... | @@ -317,12 +317,36 @@ export const makeOrchestrationIntegrationHarness = Effect.gen(function* () { | ... | @@ -317,12 +317,36 @@ export const makeOrchestrationIntegrationHarness = Effect.gen(function* () { |
| 317 | } | 317 | } |
| 318 | disposed = true; | 318 | disposed = true; |
| 319 | 319 | ||
| 320 | yield* Effect.promise(() => runtime.runPromise(providerService.stopAll())); | 320 | const shutdown = Effect.gen(function* () { |
| 321 | yield* Effect.promise(() => Effect.runPromise(Scope.close(scope, Exit.void))); | 321 | const stopAllExit = yield* Effect.exit( |
| 322 | yield* Effect.promise(() => runtime.dispose()); | 322 | Effect.promise(() => runtime.runPromise(providerService.stopAll())), |
| 323 | yield* Effect.sync(() => { | 323 | ); |
| 324 | fs.rmSync(rootDir, { recursive: true, force: true }); | 324 | const closeScopeExit = yield* Effect.exit( |
| 325 | Effect.promise(() => Effect.runPromise(Scope.close(scope, Exit.void))), | ||
| 326 | ); | ||
| 327 | const disposeRuntimeExit = yield* Effect.exit(Effect.promise(() => runtime.dispose())); | ||
| 328 | |||
| 329 | const failureCause = | ||
| 330 | Exit.isFailure(stopAllExit) | ||
| 331 | ? stopAllExit.cause | ||
| 332 | : Exit.isFailure(closeScopeExit) | ||
| 333 | ? closeScopeExit.cause | ||
| 334 | : Exit.isFailure(disposeRuntimeExit) | ||
| 335 | ? disposeRuntimeExit.cause | ||
| 336 | : null; | ||
| 337 | |||
| 338 | if (failureCause) { | ||
| 339 | yield* Effect.failCause(failureCause); | ||
| 340 | } | ||
| 325 | }); | 341 | }); |
| 342 | |||
| 343 | yield* shutdown.pipe( | ||
| 344 | Effect.ensuring( | ||
| 345 | Effect.sync(() => { | ||
| 346 | fs.rmSync(rootDir, { recursive: true, force: true }); | ||
| 347 | }), | ||
| 348 | ), | ||
| 349 | ); | ||
| 326 | }); | 350 | }); |
| 327 | 351 | ||
| 328 | return { | 352 | return { |
apps/server/integration/orchestrationEngine.integration.test.ts+19-1| ... | @@ -13,7 +13,7 @@ import { | ... | @@ -13,7 +13,7 @@ import { |
| 13 | ThreadId, | 13 | ThreadId, |
| 14 | } from "@t3tools/contracts"; | 14 | } from "@t3tools/contracts"; |
| 15 | import { assert, it } from "@effect/vitest"; | 15 | import { assert, it } from "@effect/vitest"; |
| 16 | import { Effect, Option } from "effect"; | 16 | import { Effect, Exit, Option } from "effect"; |
| 17 | 17 | ||
| 18 | import type { TestTurnResponse } from "./TestProviderAdapter.integration.ts"; | 18 | import type { TestTurnResponse } from "./TestProviderAdapter.integration.ts"; |
| 19 | import { | 19 | import { |
| ... | @@ -133,6 +133,24 @@ const startTurn = (input: { | ... | @@ -133,6 +133,24 @@ const startTurn = (input: { |
| 133 | createdAt: nowIso(), | 133 | createdAt: nowIso(), |
| 134 | }); | 134 | }); |
| 135 | 135 | ||
| 136 | it.live("always removes integration temp dirs when shutdown fails", () => | ||
| 137 | Effect.gen(function* () { | ||
| 138 | const harness = yield* makeOrchestrationIntegrationHarness; | ||
| 139 | const rootDir = harness.rootDir; | ||
| 140 | const originalStopAll = harness.providerService.stopAll.bind(harness.providerService); | ||
| 141 | |||
| 142 | ( | ||
| 143 | harness.providerService as unknown as { | ||
| 144 | stopAll: typeof harness.providerService.stopAll; | ||
| 145 | } | ||
| 146 | ).stopAll = () => originalStopAll().pipe(Effect.andThen(Effect.fail(new Error("boom")))); | ||
| 147 | |||
| 148 | const disposeExit = yield* Effect.exit(harness.dispose); | ||
| 149 | assert.equal(Exit.isFailure(disposeExit), true); | ||
| 150 | assert.equal(fs.existsSync(rootDir), false); | ||
| 151 | }), | ||
| 152 | ); | ||
| 153 | |||
| 136 | it.live("runs a single turn end-to-end and persists checkpoint state in sqlite + git", () => | 154 | it.live("runs a single turn end-to-end and persists checkpoint state in sqlite + git", () => |
| 137 | withHarness((harness) => | 155 | withHarness((harness) => |
| 138 | Effect.gen(function* () { | 156 | Effect.gen(function* () { |