Sub-scope 08 (mongo) changestream - manifest and negative-space probes revision=a24ece9cf797f7ea647e33bf846b115208ed1ba5 generatedAt=2026-08-30T00:15:09+00:00 cwd=/shared/codebase/clean-architecture-backend-template/src === OWNED FILES === 55410c37d99e15fd487d45057a2f44fe2b656582 main/java/dev/caskeleton/adapter/outbound/mongo/changestream/MongoChangeEventIdentity.java 97 2a41c567b37fd2cd0f7adcbd0cf1db3583f020bc main/java/dev/caskeleton/adapter/outbound/mongo/changestream/MongoChangeStreamPipeline.java 85 462398af539336129b4a1f0cf669a94eb0c52120 main/java/dev/caskeleton/adapter/outbound/mongo/changestream/MongoChangeStreamState.java 35 d6a137168ebf884948907dc09a86833bc68a1983 main/java/dev/caskeleton/adapter/outbound/mongo/changestream/MongoChangeStreamSubscription.java 50 b9cb4a344e54678c7c02fc48e71851fc2e84e79c main/java/dev/caskeleton/adapter/outbound/mongo/changestream/MongoClusterTime.java 53 240d41177c0bf08b810928ed61dbdd07874b4a43 main/java/dev/caskeleton/adapter/outbound/mongo/changestream/MongoResumeCheckpoint.java 100 ccac8af549c5ed7b23ed3be2fa1ad68cc1ef5f48 main/java/dev/caskeleton/adapter/outbound/mongo/changestream/MongoResumeCheckpointStore.java 24 22ee8f0391d93b109f3ffa763f1a2acc03564de8 main/java/dev/caskeleton/adapter/outbound/mongo/changestream/MongoResumePosition.java 18 52b776c4178d40bae2e7b48cfcff2207c25cc3ab main/java/dev/caskeleton/adapter/outbound/mongo/changestream/MongoResumeTokenCodec.java 38 76e710168870ef9d9e8a464dba087e247315ccff main/java/dev/caskeleton/adapter/outbound/mongo/changestream/consumer/MongoChangeStreamSource.java 54 5d2c65139d86fefc8f341a120dd3abdff345f368 main/java/dev/caskeleton/adapter/outbound/mongo/changestream/consumer/ReactiveMongoChangeStreamConsumer.java 221 6d2bd659cc831094d7854fcfc4560846ff03927a main/java/dev/caskeleton/adapter/outbound/mongo/changestream/consumer/SpringReactiveChangeStreamSource.java 71 8a7ba018966876acb9c64b9e7906ba11407f0c44 main/java/dev/caskeleton/adapter/outbound/mongo/changestream/projector/MongoChangeClaim.java 25 43032013612390ba42527cfa06d5c74f47409a42 main/java/dev/caskeleton/adapter/outbound/mongo/changestream/projector/MongoChangeDeduplicationStore.java 50 ba15c66e206050463073a2c373e88936d6ec6f36 main/java/dev/caskeleton/adapter/outbound/mongo/changestream/projector/MongoChangeProjectionResult.java 65 75e656da9f039e0124dee52b0e4b55555429c943 main/java/dev/caskeleton/adapter/outbound/mongo/changestream/projector/MongoChangeProjector.java 23 8a49dd99ecf66f528ddba3f17c38f5594d3dc3d4 main/java/dev/caskeleton/adapter/outbound/mongo/changestream/projector/MongoChangeStreamRunner.java 124 a956f31fd1f2779fc1063cfd1407ab098f65bb82 main/java/dev/caskeleton/adapter/outbound/mongo/changestream/recovery/MongoChangeHistoryLostException.java 35 8b02975c382f8df615b19260076554c64aaabf66 main/java/dev/caskeleton/adapter/outbound/mongo/changestream/recovery/MongoChangeStreamRecoveryDecision.java 36 fe947e6c533518c9560650b808cc29b1b0e23ae6 main/java/dev/caskeleton/adapter/outbound/mongo/changestream/recovery/MongoChangeStreamRecoveryPolicy.java 67 88ca505e553bf03721e270d95cf43de6446f5406 main/java/dev/caskeleton/adapter/outbound/mongo/changestream/recovery/MongoInvalidateRecovery.java 46 c51cc24680d0b2cd33c18ca36d4c87f6518287b8 test/java/dev/caskeleton/adapter/outbound/mongo/changestream/MongoChangeStreamPipelineTest.java 153 60a0312b904e8b8ecee216bc84942c2ce8fbe4cc test/java/dev/caskeleton/adapter/outbound/mongo/changestream/MongoResumeCheckpointStoreTest.java 85 a9c7b48469df8438d48ae3edfce562c692c99ab8 test/java/dev/caskeleton/adapter/outbound/mongo/changestream/consumer/ChangeStreamConsumerLifecycleTest.java 332 39c83409212c07edc25c72849e35a3488d3fe7df test/java/dev/caskeleton/adapter/outbound/mongo/changestream/projector/MongoChangeStreamRunnerTest.java 357 d20e9e4712dae2642af4308eda984f84ff0ce8b0 test/java/dev/caskeleton/adapter/outbound/mongo/changestream/recovery/MongoChangeStreamRecoveryPolicyTest.java 69 production: 21 test: 5 production LOC: 1317 total === 8.1 reachability: beans and cross-package production references === $ grep -n 'ChangeStream\|changestream\|Projector\|Resume' adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/autoconfigure/MongoPlatformAutoConfiguration.java 209: public dev.caskeleton.adapter.outbound.mongo.changestream.consumer.MongoChangeStreamSource 210: mongoChangeStreamSource( 213: return new dev.caskeleton.adapter.outbound.mongo.changestream.consumer 214: .SpringReactiveChangeStreamSource(template, collections); 228: dev.caskeleton.adapter.outbound.mongo.changestream.MongoChangeStreamSubscription.class, 229: dev.caskeleton.adapter.outbound.mongo.changestream.MongoResumeCheckpointStore.class, 230: dev.caskeleton.adapter.outbound.mongo.changestream.MongoResumeTokenCodec.class, 231: dev.caskeleton.adapter.outbound.mongo.changestream.projector.MongoChangeProjector.class, 232: dev.caskeleton.adapter.outbound.mongo.changestream.projector.MongoChangeDeduplicationStore 235: public dev.caskeleton.adapter.outbound.mongo.changestream.consumer 236: .ReactiveMongoChangeStreamConsumer 237: reactiveMongoChangeStreamConsumer( 238: dev.caskeleton.adapter.outbound.mongo.changestream.MongoChangeStreamSubscription 240: dev.caskeleton.adapter.outbound.mongo.changestream.consumer.MongoChangeStreamSource 242: dev.caskeleton.adapter.outbound.mongo.changestream.MongoResumeCheckpointStore 244: dev.caskeleton.adapter.outbound.mongo.changestream.MongoResumeTokenCodec tokens, 245: dev.caskeleton.adapter.outbound.mongo.changestream.projector.MongoChangeProjector 247: dev.caskeleton.adapter.outbound.mongo.changestream.projector 250: return new dev.caskeleton.adapter.outbound.mongo.changestream.consumer 251: .ReactiveMongoChangeStreamConsumer( 256: new dev.caskeleton.adapter.outbound.mongo.changestream.MongoChangeStreamPipeline( 257: new dev.caskeleton.adapter.outbound.mongo.changestream.projector 258: .MongoChangeStreamRunner(projector, deduplication, checkpoints)), 259: new dev.caskeleton.adapter.outbound.mongo.changestream.recovery 260: .MongoChangeStreamRecoveryPolicy(), 261: new dev.caskeleton.adapter.outbound.mongo.changestream.recovery exit=0 $ git grep -n 'changeStreams' adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/autoconfigure/MongoPlatformSettings.java adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/autoconfigure/MongoPlatformAutoConfiguration.java adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/autoconfigure/MongoStartupValidator.java adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/autoconfigure/MongoPlatformAutoConfiguration.java:363: properties.changeStreams(), adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/autoconfigure/MongoPlatformSettings.java:22: * assuming it. Both it and {@code changeStreams} used to be literal {@code true}s passed into adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/autoconfigure/MongoPlatformSettings.java:30: boolean changeStreams, adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/autoconfigure/MongoPlatformSettings.java:55: changeStreams = false; adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/autoconfigure/MongoStartupValidator.java:34: private final boolean changeStreamsEnabled; adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/autoconfigure/MongoStartupValidator.java:44: boolean changeStreamsEnabled, adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/autoconfigure/MongoStartupValidator.java:51: this.changeStreamsEnabled = changeStreamsEnabled; adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/autoconfigure/MongoStartupValidator.java:104: if (changeStreamsEnabled && !capabilities.isStable(MongoCapability.CHANGE_STREAM)) { exit=0 $ git grep -ln 'changestream' -- 'adapter/outbound/persistence-mongo/src/main' | grep -v '/changestream/' adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/advanced/bridge/MongoChangeMessagingBridge.java adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/advanced/bridge/MongoChangeToIntegrationEventMapper.java adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/autoconfigure/MongoPlatformAutoConfiguration.java exit=0 $ git grep -n 'new ReactiveMongoChangeStreamConsumer\|new SpringReactiveChangeStreamSource\|new MongoChangeStreamPipeline\|new MongoChangeStreamRunner' -- 'adapter/outbound/persistence-mongo/src/main' adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/changestream/consumer/ReactiveMongoChangeStreamConsumer.java:145: new MongoChangeStreamPipeline.MongoChangeEvent( exit=0 === 8.1b implementations of the three SPIs a deployment must supply === $ git grep -n 'implements MongoResumeTokenCodec\|implements MongoResumeCheckpointStore\|implements MongoChangeDeduplicationStore\|implements MongoChangeProjector' -- 'adapter/outbound/persistence-mongo/src' adapter/outbound/persistence-mongo/src/test/java/dev/caskeleton/adapter/outbound/mongo/advanced/bridge/MongoChangeMessagingBridgeTest.java:115: private static final class CountingCheckpointStore implements MongoResumeCheckpointStore { adapter/outbound/persistence-mongo/src/test/java/dev/caskeleton/adapter/outbound/mongo/changestream/MongoChangeStreamPipelineTest.java:117: private static final class AlwaysClaims implements MongoChangeDeduplicationStore { adapter/outbound/persistence-mongo/src/test/java/dev/caskeleton/adapter/outbound/mongo/changestream/MongoChangeStreamPipelineTest.java:136: private static final class NoOpCheckpoints implements MongoResumeCheckpointStore { adapter/outbound/persistence-mongo/src/test/java/dev/caskeleton/adapter/outbound/mongo/changestream/consumer/ChangeStreamConsumerLifecycleTest.java:211: private static final class PlainTextCodec implements MongoResumeTokenCodec { adapter/outbound/persistence-mongo/src/test/java/dev/caskeleton/adapter/outbound/mongo/changestream/consumer/ChangeStreamConsumerLifecycleTest.java:261: private static final class RecordingCheckpointStore implements MongoResumeCheckpointStore { adapter/outbound/persistence-mongo/src/test/java/dev/caskeleton/adapter/outbound/mongo/changestream/consumer/ChangeStreamConsumerLifecycleTest.java:295: private final class CountingProjector implements MongoChangeProjector { adapter/outbound/persistence-mongo/src/test/java/dev/caskeleton/adapter/outbound/mongo/changestream/consumer/ChangeStreamConsumerLifecycleTest.java:314: private static final class AlwaysClaims implements MongoChangeDeduplicationStore { adapter/outbound/persistence-mongo/src/test/java/dev/caskeleton/adapter/outbound/mongo/changestream/projector/MongoChangeStreamRunnerTest.java:101: private static final class RecordingCheckpointStore implements MongoResumeCheckpointStore { adapter/outbound/persistence-mongo/src/test/java/dev/caskeleton/adapter/outbound/mongo/changestream/projector/MongoChangeStreamRunnerTest.java:290: private static final class ClaimingDeduplicationStore implements MongoChangeDeduplicationStore { adapter/outbound/persistence-mongo/src/test/java/dev/caskeleton/adapter/outbound/mongo/changestream/projector/MongoChangeStreamRunnerTest.java:339: private static final class EmptyDeduplicationStore implements MongoChangeDeduplicationStore { exit=0 === 8.2 the high-water mark: set before processing, kept across a resume === $ grep -n 'advancesPosition\|getAndAccumulate\|highWaterMark' adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/MongoChangeStreamPipeline.java 2>/dev/null || grep -n 'advancesPosition\|getAndAccumulate\|highWaterMark' adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/changestream/MongoChangeStreamPipeline.java 32: private final AtomicReference highWaterMark = new AtomicReference<>(); 49: if (!advancesPosition(event.clusterTime())) { 58: private boolean advancesPosition(MongoClusterTime candidate) { 60: highWaterMark.getAndAccumulate( 67: public MongoClusterTime highWaterMark() { 68: return highWaterMark.get(); exit=0 $ grep -n 'allowsCheckpointAdvance' -B 8 adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/changestream/projector/MongoChangeProjectionResult.java 38- } 39- 40- /** The event could not be interpreted and was routed to the parking workflow. */ 41- public static MongoChangeProjectionResult parked(String reason) { 42- return new MongoChangeProjectionResult(Outcome.PARKED, reason); 43- } 44- 45- /** True when the checkpoint may advance past this event. */ 46: public boolean allowsCheckpointAdvance() { exit=0 $ grep -n 'recoverFrom\|openAndConsume\|Flux.defer' adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/changestream/consumer/ReactiveMongoChangeStreamConsumer.java 87: .thenMany(Flux.defer(this::openAndConsume)) 88: .onErrorResume(this::recoverFrom); 101: private Flux openAndConsume() { 188: private Flux recoverFrom(Throwable failure) { 199: return Flux.defer(this::openAndConsume).onErrorResume(this::haltOn); exit=0 === 8.2b does any test cover a redelivery after a resume? === $ grep -n 'aResumableFailureReopensFromTheCheckpoint' -A 16 adapter/outbound/persistence-mongo/src/test/java/dev/caskeleton/adapter/outbound/mongo/changestream/consumer/ChangeStreamConsumerLifecycleTest.java 93: void aResumableFailureReopensFromTheCheckpoint() { 94- checkpoints.stored = Optional.of(checkpoint("token-1", MongoResumePosition.RESUME_AFTER)); 95- MongoQueryException resumable = queryFailure(133, "ResumableChangeStreamError"); 96- RecordingSource source = 97- new RecordingSource( 98- Flux.error(resumable), Flux.just(event("token-2", 2, OperationType.INSERT))); 99- 100- ReactiveMongoChangeStreamConsumer consumer = consumer(source); 101- StepVerifier.create(consumer.run()).expectNextCount(1).verifyComplete(); 102- 103- assertThat(source.opens).isEqualTo(2); 104- assertThat(source.reopenedAt.orElseThrow().token().getString("_data").getValue()) 105- .as("the last written position is the last event a projection completed for") 106- .isEqualTo("token-1"); 107- } 108- 109- @Test exit=0 $ grep -n 'anEventBehindTheHighWaterMarkIsDropped' -A 24 adapter/outbound/persistence-mongo/src/test/java/dev/caskeleton/adapter/outbound/mongo/changestream/MongoChangeStreamPipelineTest.java 62: void anEventBehindTheHighWaterMarkIsDropped() { 63- List projected = new ArrayList<>(); 64- MongoChangeStreamPipeline pipeline = 65- new MongoChangeStreamPipeline( 66- new MongoChangeStreamRunner( 67- (identity, change) -> { 68- projected.add(change.getString("id").getValue()); 69- return Mono.just(MongoChangeProjectionResult.applied()); 70- }, 71- new AlwaysClaims(), 72- new NoOpCheckpoints())); 73- 74- pipeline 75- .process(Flux.just(event("a", 5), event("late", 2), event("b", 6))) 76- .collectList() 77- .block(); 78- 79- assertThat(projected) 80- .as("running it would move the resume checkpoint backwards over applied work") 81- .containsExactly("a", "b"); 82- assertThat(pipeline.highWaterMark()).isEqualTo(new MongoClusterTime(1_700_000_000L, 6)); 83- } 84- 85- @Test 86- @DisplayName("cluster time compares numerically, not as text") exit=0 $ grep -n 'aBusyClaimNeverAdvancesTheCheckpoint' -A 22 adapter/outbound/persistence-mongo/src/test/java/dev/caskeleton/adapter/outbound/mongo/changestream/projector/MongoChangeStreamRunnerTest.java 173: void aBusyClaimNeverAdvancesTheCheckpoint() { 174- RecordingCheckpointStore checkpoints = new RecordingCheckpointStore(); 175- ClaimingDeduplicationStore deduplication = new ClaimingDeduplicationStore(); 176- deduplication.claimFor(IDENTITY); 177- 178- MongoChangeStreamRunner runner = 179- new MongoChangeStreamRunner( 180- (identity, change) -> Mono.just(MongoChangeProjectionResult.applied()), 181- deduplication, 182- checkpoints); 183- 184- MongoChangeProjectionResult result = 185- runner.runOne(IDENTITY, new BsonDocument(), CHECKPOINT).block(); 186- 187- assertThat(result.outcome()).isEqualTo(MongoChangeProjectionResult.Outcome.CLAIMED_ELSEWHERE); 188- assertThat(checkpoints.saveCount()) 189- .as("the holder may still fail, and the stream never replays what the checkpoint passed") 190- .isZero(); 191- } 192- 193- @Test 194- @org.junit.jupiter.api.DisplayName("a crash after claiming leaves the event retryable") 195- void aCrashAfterClaimingLeavesTheEventRetryable() { exit=0 === 8.3 duplicate mechanisms inside recovery === $ git grep -n 'onHistoryLost\|onResumableFailure\|onInvalidate\|checkpointFor\|requireCorrectResumeOption\|autoResumable\|MongoChangeHistoryLostException' -- 'adapter/outbound/persistence-mongo/src' adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/advanced/tenancy/database/MongoTenantMigrationCoordinator.java:85: public Optional checkpointFor(String tenantKey) { adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/changestream/MongoChangeStreamState.java:32: public boolean autoResumable() { adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/changestream/consumer/ReactiveMongoChangeStreamConsumer.java:119: invalidates.requireCorrectResumeOption(checkpoint, checkpoint.position()); adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/changestream/recovery/MongoChangeHistoryLostException.java:16:public final class MongoChangeHistoryLostException extends MongoPersistenceException { adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/changestream/recovery/MongoChangeHistoryLostException.java:22: public MongoChangeHistoryLostException( adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/changestream/recovery/MongoChangeStreamRecoveryPolicy.java:29: public MongoChangeStreamRecoveryDecision onHistoryLost(String subscriptionProfile) { adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/changestream/recovery/MongoChangeStreamRecoveryPolicy.java:36: public MongoChangeStreamRecoveryDecision onResumableFailure() { adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/changestream/recovery/MongoChangeStreamRecoveryPolicy.java:46: public MongoChangeStreamRecoveryDecision onInvalidate() { adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/changestream/recovery/MongoInvalidateRecovery.java:19: public MongoResumeCheckpoint checkpointFor(String subscriptionProfile, byte[] invalidateToken) { adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/changestream/recovery/MongoInvalidateRecovery.java:31: public void requireCorrectResumeOption( adapter/outbound/persistence-mongo/src/test/java/dev/caskeleton/adapter/outbound/mongo/changestream/recovery/MongoChangeStreamRecoveryPolicyTest.java:21: MongoChangeStreamRecoveryDecision decision = policy.onHistoryLost("orders"); adapter/outbound/persistence-mongo/src/test/java/dev/caskeleton/adapter/outbound/mongo/changestream/recovery/MongoChangeStreamRecoveryPolicyTest.java:31: assertThat(policy.onResumableFailure().autoResume()).isTrue(); adapter/outbound/persistence-mongo/src/test/java/dev/caskeleton/adapter/outbound/mongo/changestream/recovery/MongoChangeStreamRecoveryPolicyTest.java:55: MongoResumeCheckpoint checkpoint = recovery.checkpointFor("orders", new byte[] {1, 2, 3}); adapter/outbound/persistence-mongo/src/test/java/dev/caskeleton/adapter/outbound/mongo/changestream/recovery/MongoChangeStreamRecoveryPolicyTest.java:59: () -> recovery.requireCorrectResumeOption(checkpoint, MongoResumePosition.RESUME_AFTER)) adapter/outbound/persistence-mongo/src/test/java/dev/caskeleton/adapter/outbound/mongo/changestream/recovery/MongoChangeStreamRecoveryPolicyTest.java:65: assertThat(MongoChangeStreamState.RUNNING.autoResumable()).isTrue(); adapter/outbound/persistence-mongo/src/test/java/dev/caskeleton/adapter/outbound/mongo/changestream/recovery/MongoChangeStreamRecoveryPolicyTest.java:66: assertThat(MongoChangeStreamState.HISTORY_LOST.autoResumable()).isFalse(); adapter/outbound/persistence-mongo/src/test/java/dev/caskeleton/adapter/outbound/mongo/changestream/recovery/MongoChangeStreamRecoveryPolicyTest.java:67: assertThat(MongoChangeStreamState.FAILED.autoResumable()).isFalse(); exit=0 === 8.4 documentation drift: the runbooks the policy names === $ ls ../docs/mongodb/runbooks/ 2>&1 failover.md history-lost.md unknown-commit.md exit=0 $ grep -rn 'HISTORY_LOST_RUNBOOK\|FAILURE_RUNBOOK' adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/changestream/recovery/MongoChangeStreamRecoveryPolicy.java 18: public static final String HISTORY_LOST_RUNBOOK = "docs/mongodb/runbooks/history-lost.md"; 21: public static final String FAILURE_RUNBOOK = "docs/mongodb/runbooks/failover.md"; 32: MongoChangeStreamState.HISTORY_LOST, HISTORY_LOST_RUNBOOK); 55: MongoChangeStreamState.HISTORY_LOST, HISTORY_LOST_RUNBOOK); 60: return MongoChangeStreamRecoveryDecision.halt(MongoChangeStreamState.FAILED, FAILURE_RUNBOOK); exit=0 $ find .. -name 'history-lost.md' -o -name 'failover.md' | head ../docs/mongodb/runbooks/failover.md ../docs/mongodb/runbooks/history-lost.md exit=0