Sub-scope 02 (mongo) negative-space probes: api surface reachability, invariant siblings, duplicate mechanisms, drift
revision=a24ece9cf797f7ea647e33bf846b115208ed1ba5
executedAt=2026-08-29T23:31:37+00:00
cwd=/shared/codebase/clean-architecture-backend-template/src
observation boundary: tracked sources at this revision. api/** is the intended external
surface, so a zero in-repository reference is not evidence of dead code.

=== 8.1 api types referenced from outside the owning leaf ===
$ git grep -l 'outbound\.mongo\.api\.' -- . ':!adapter/outbound/persistence-mongo/**'
exit=1

=== 8.1b framework-free rule: any Spring/driver/BSON/Reactor import under api/** ===
$ git grep -n 'import org\.springframework\|import com\.mongodb\|import org\.bson\|import reactor\.' -- 'adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/api'
exit=1

=== 8.2 invariant siblings: which api records refuse an incoherent construction ===
$ grep -n 'requireCommitUnknown\|requireTransient' adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/api/error/MongoTransactionCommitUnknownException.java adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/api/error/MongoTransactionTransientException.java
adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/api/error/MongoTransactionCommitUnknownException.java:32:        requireCommitUnknown(failureContext));
adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/api/error/MongoTransactionCommitUnknownException.java:36:  private static MongoFailureContext requireCommitUnknown(MongoFailureContext context) {
adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/api/error/MongoTransactionTransientException.java:30:        requireTransient(failureContext));
adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/api/error/MongoTransactionTransientException.java:33:  private static MongoFailureContext requireTransient(MongoFailureContext context) {
exit=0

$ sed -n '30,55p' adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/api/error/MongoFailureContext.java
    int attempt,
    Duration elapsed,
    String traceId) {

  public MongoFailureContext {
    Objects.requireNonNull(scope, "scope");
    Objects.requireNonNull(operationType, "operationType");
    Objects.requireNonNull(consistencyProfile, "consistencyProfile");
    Objects.requireNonNull(category, "category");
    Objects.requireNonNull(outcome, "outcome");
    Objects.requireNonNull(errorLabels, "errorLabels");
    Objects.requireNonNull(serverCode, "serverCode");
    Objects.requireNonNull(elapsed, "elapsed");
    Objects.requireNonNull(traceId, "traceId");
    errorLabels = Set.copyOf(errorLabels);
    if (attempt < 1) {
      throw new IllegalArgumentException("attempt must be at least 1");
    }
    if (elapsed.isNegative()) {
      throw new IllegalArgumentException("elapsed must not be negative");
    }
  }

  /**
   * The commit-unknown context of design §14.3.
   *
exit=0

$ grep -n 'isAmbiguous\|forbidsBlindReplay' adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/api/error/MongoExecutionOutcome.java
45:  public boolean isAmbiguous() {
50:  public boolean forbidsBlindReplay() {
51:    return isAmbiguous() || this == PARTIAL_BULK_WRITE;
exit=0

=== 8.2b the no-cause rule: the root's claim, the one constructor that breaks it, its call site, and the test ===
$ grep -n 'no constructor accepts' adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/api/error/MongoPersistenceException.java
11: * query parameter into an exception message. Second, no constructor accepts a {@link Throwable}
exit=0

$ grep -n 'initCause' adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/api/error/MongoTimeoutException.java
27:    initCause(cause);
exit=0

$ git grep -n 'MongoTimeoutException(' -- 'adapter/outbound/persistence-mongo/src/main'
adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/api/error/MongoTimeoutException.java:16:  public MongoTimeoutException(MongoFailureContext failureContext) {
adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/api/error/MongoTimeoutException.java:25:  public MongoTimeoutException(MongoFailureContext failureContext, Throwable cause) {
adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/failure/DefaultMongoFailureTranslator.java:104:      case TIMEOUT -> new MongoTimeoutException(failureContext);
adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/reactive/DefaultReactiveMongoExecutor.java:152:          new dev.caskeleton.adapter.outbound.mongo.api.error.MongoTimeoutException(
exit=0

$ grep -n 'exceptionsDoNotExposeADriverCause' -A 10 adapter/outbound/persistence-mongo/src/test/java/dev/caskeleton/adapter/outbound/mongo/api/error/MongoFailureContextTest.java
58:  void exceptionsDoNotExposeADriverCause() {
59-    MongoPersistenceException exception =
60-        new MongoTransactionCommitUnknownException(
61-            MongoFailureContext.commitUnknown(
62-                new MongoOperationName("order.reserve"), "251", Duration.ZERO),
63-            "read the transaction record");
64-
65-    assertThat(exception.getCause()).isNull();
66-    assertThat(exception.category()).isEqualTo(MongoFailureCategory.TRANSACTION_COMMIT_UNKNOWN);
67-  }
68-}
exit=0

=== 8.2c schema-version failure: dedicated category, and what the two construction sites attach ===
$ grep -n 'SCHEMA_VERSION_UNSUPPORTED' adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/api/error/MongoFailureCategory.java
68:  SCHEMA_VERSION_UNSUPPORTED,
exit=0

$ git grep -n 'new MongoDataSchemaUnsupportedException(' -- 'adapter/outbound/persistence-mongo/src/main'
adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/api/schema/MongoSchemaVersionPolicy.java:85:    return new MongoDataSchemaUnsupportedException(
adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/failure/DefaultMongoFailureTranslator.java:111:          new MongoDataSchemaUnsupportedException(failureContext, -1, -1, -1);
exit=0

$ sed -n '80,90p' adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/api/schema/MongoSchemaVersionPolicy.java
  }

  private MongoDataSchemaUnsupportedException unsupported(DocumentSchemaVersion version) {
    MongoFailureContext context =
        MongoFailureContext.rejected(new MongoOperationName("schema.version-check"));
    return new MongoDataSchemaUnsupportedException(
        context, version.value(), range.minimumSupported().value(), range.current().value());
  }
}
exit=0

$ grep -n 'rejected(' -A 14 adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/api/error/MongoFailureContext.java | grep -n 'OPERATION_REJECTED\|NOT_SENT'
6:140-        MongoFailureCategory.OPERATION_REJECTED,
7:141-        MongoExecutionOutcome.NOT_SENT,
exit=0

$ sed -n '104,112p' adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/failure/DefaultMongoFailureTranslator.java
      case TIMEOUT -> new MongoTimeoutException(failureContext);
      case CURSOR -> new MongoCursorException(failureContext);
      case DOCUMENT_TOO_LARGE -> new MongoDocumentTooLargeException(failureContext, -1L, -1L);
      case SHARD_ROUTING -> new MongoShardRoutingException(failureContext);
      case RESUME -> new MongoResumeException(failureContext);
      case ENCRYPTION -> new MongoEncryptionException(failureContext);
      case SCHEMA_VERSION_UNSUPPORTED ->
          new MongoDataSchemaUnsupportedException(failureContext, -1, -1, -1);
      // Bulk partial failure and local rejection are raised by the layers that own their detail —
exit=0

=== 8.3 duplicate mechanism: the two profile-name records ===
$ diff <(sed 's/Database/X/g;s/database/x/g' adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/api/DatabaseProfileName.java) <(sed 's/Collection/X/g;s/collection/x/g' adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/api/CollectionProfileName.java)
6c6
<  * Registered logical name of a x profile (design §7.1, §28).
---
>  * Registered logical name of a x profile (design §7.1, §16.2).
8,12c8,11
<  * <p>A profile name selects a configured connection, credential, consistency default and timeout
<  * set. It is never the physical x name supplied by a caller: allowing that would turn a
<  * request value into a routing decision and into a metric tag. The pattern therefore rejects
<  * slashes, whitespace and generated-identifier shapes, which is what {@code Dynamic collection
<  * profile} in the design's startup failure list means in practice.
---
>  * <p>Every operation resolves its guardrails — field allowlist, operator allowlist, budget, index
>  * manifest, TTL policy — through this name. Accepting a caller-supplied x string instead
>  * would defeat all of them at once, so the same dynamic-value rejection as {@link
>  * DatabaseProfileName} applies here.
18d16
<   /** A value that looks like a generated identifier is dynamic input, not a registered profile. */
exit=1

=== 8.3b duplicate mechanism: retry semantics carried twice ===
$ grep -n 'boolean retryable' adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/api/error/MongoFailureContext.java
26:    boolean retryable,
exit=0

$ grep -n 'Encoding that as a scope rather than' -B 2 -A 3 adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/api/error/MongoRetryScope.java
6- * <p>The design's central retry rule is that {@code TransientTransactionError} and {@code
7- * UnknownTransactionCommitResult} demand opposite responses: the first replays the whole body from
8: * a new session, the second must never replay the body at all. Encoding that as a scope rather than
9- * a {@code retryable} boolean is what stops the two from collapsing into one flag at the call site.
10- */
11-public enum MongoRetryScope {
exit=0

$ grep -n 'retryScope\|bodyReplayAllowed' adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/failure/MongoFailureClassification.java
16:    MongoFailureCategory category, MongoExecutionOutcome outcome, MongoRetryScope retryScope) {
21:    Objects.requireNonNull(retryScope, "retryScope");
22:    if (retryScope == MongoRetryScope.COMMIT_ONLY
75:  public boolean bodyReplayAllowed() {
76:    return retryScope == MongoRetryScope.WHOLE_OPERATION
77:        || retryScope == MongoRetryScope.WHOLE_TRANSACTION;
exit=0

$ grep -n 'classification.bodyReplayAllowed()\|classification.ambiguous()' adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/failure/DefaultMongoFailureTranslator.java
83:            classification.bodyReplayAllowed(),
84:            classification.ambiguous(),
exit=0

=== 8.3c the placeholder profile identity shares the value space of a real one ===
$ grep -n 'UNSPECIFIED' adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/api/MongoOperationScope.java
18:  public static final String UNSPECIFIED = "unspecified";
37:        new DatabaseProfileName(UNSPECIFIED),
38:        new CollectionProfileName(UNSPECIFIED));
43:    return !UNSPECIFIED.equals(databaseProfile.value())
44:        && !UNSPECIFIED.equals(collectionProfile.value());
exit=0

$ grep -n 'FORMAT' adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/api/DatabaseProfileName.java
16:  private static final Pattern FORMAT = Pattern.compile("[a-z][a-z0-9-]{2,63}");
23:    if (value == null || !FORMAT.matcher(value).matches() || UUID_LIKE.matcher(value).matches()) {
exit=0

=== 8.4 consistency descriptor: which incoherent pairings the record refuses ===
$ sed -n '28,42p' adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/api/consistency/MongoConsistencyDescriptor.java
    Objects.requireNonNull(profile, "profile");
    Objects.requireNonNull(readPreference, "readPreference");
    Objects.requireNonNull(readConcern, "readConcern");
    Objects.requireNonNull(writeConcern, "writeConcern");
    Objects.requireNonNull(guarantee, "guarantee");
    if (requiresCausalSession && !"majority".equals(readConcern)) {
      throw new IllegalArgumentException(
          "a causal session profile requires majority read concern: " + profile);
    }
    if (requiresCausalSession && !"majority".equals(writeConcern)) {
      throw new IllegalArgumentException(
          "a causal session profile requires majority write concern: " + profile);
    }
  }

exit=0

$ grep -n 'has not chosen durability' -B 3 -A 1 adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/api/consistency/MongoConsistencyProfile.java
5- *
6- * <p>These are profiles rather than three independent knobs because read preference, read concern
7- * and write concern only mean something together. A caller that picks {@code majority} write
8: * concern and {@code secondaryPreferred} reads has not chosen durability, it has chosen a bug.
9- *
exit=0

