# revision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916

## A. word-match counts outside the owning leaf (RAW — see B for the correction)
MessagingObservation                       2
MessagingMetrics                           0
MessagingTags                              2
MessagingRedactor                          1
CardinalityGuard                           10
MessagingTracer                            0
MessagingAuditSink                         0
MessagingAuditEvent                        3
DefaultMessagingObservationConvention      0

## B. CardinalityGuard is two different classes with the same simple name
src/application-core/src/main/java/dev/caskeleton/application/notification/platform/observation/CardinalityGuard.java:24:public final class CardinalityGuard {
src/messaging/messaging-observability/src/main/java/dev/caskeleton/messaging/observation/CardinalityGuard.java:19:public final class CardinalityGuard {
--- which package each importer actually names
src/adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/platform/observation/LoggingNotificationMetrics.java:3:import dev.caskeleton.application.notification.platform.observation.CardinalityGuard;
src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformObservabilityConfig.java:13:import dev.caskeleton.application.notification.platform.observation.CardinalityGuard;
src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/observation/MicrometerNotificationMetrics.java:3:import dev.caskeleton.application.notification.platform.observation.CardinalityGuard;
src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/observation/NotificationObservationConvention.java:3:import dev.caskeleton.application.notification.platform.observation.CardinalityGuard;
src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/notification/observation/NotificationObservationTest.java:14:import dev.caskeleton.application.notification.platform.observation.CardinalityGuard;
src/messaging/messaging-spring-boot-starter/src/main/java/dev/caskeleton/messaging/autoconfigure/MessagingCoreAutoConfiguration.java:6:import dev.caskeleton.messaging.observation.CardinalityGuard;
--- so the messaging CardinalityGuard has ONE external importer, not ten.

## C. types with no consumer outside the owning leaf
MessagingMetrics                           NONE
MessagingTracer                            NONE
MessagingAuditSink                         NONE
DefaultMessagingObservationConvention      NONE

## D. the tag vocabulary exists to be the single spelling — and the one call site bypasses it
--- the convention's publish() fills failureCategory
  public MessagingTags publish(
      String broker,
      String destinationProfile,
      PublishCompletion completion,
      Optional<FailureCategory> failure) {
    Objects.requireNonNull(completion, "completion must not be null");
    Objects.requireNonNull(failure, "failure must not be null");
    return new MessagingTags(
        broker,
        destinationProfile,
        PUBLISH,
        lower(completion.name()),
        failure.map(category -> lower(category.name())).orElse(MessagingTags.NONE),
        MessagingTags.NONE);
  }
--- MessagingTags.of() forces failureCategory=NONE and retryStage=NONE
  public static MessagingTags of(
      String broker, String destinationProfile, String operation, String outcome) {
    return new MessagingTags(broker, destinationProfile, operation, outcome, NONE, NONE);
  }
--- the only production call site uses the literal and the 4-argument factory
  /** Records one publish against bounded dimensions: broker, destination, operation, outcome. */
  private void observe(DestinationProfile profile, PublishResult result, long startedAt) {
    observation.recordPublish(
        dev.caskeleton.messaging.observation.MessagingTags.of(
            profile.broker(),
            profile.name().value(),
            "publish",
            result.completion().name().toLowerCase(java.util.Locale.ROOT)),
        elapsedSince(startedAt));
  }

## E. the audit sink interface is re-declared by its only user
--- observability declares it
18:public interface MessagingAuditSink {
--- admin-runtime uses the event type but declares its own sink
src/messaging/messaging-admin-runtime/src/main/java/dev/caskeleton/messaging/admin/runtime/RedriveService.java:126:          new dev.caskeleton.messaging.observation.MessagingAuditEvent(
src/messaging/messaging-admin-runtime/src/main/java/dev/caskeleton/messaging/admin/runtime/RedriveService.java:208:    void record(dev.caskeleton.messaging.observation.MessagingAuditEvent event);
src/messaging/messaging-admin-runtime/src/main/java/dev/caskeleton/messaging/admin/runtime/ReplayService.java:7:import dev.caskeleton.messaging.observation.MessagingAuditEvent;
src/messaging/messaging-admin-runtime/src/main/java/dev/caskeleton/messaging/admin/runtime/ReplayService.java:73:        new MessagingAuditEvent(
208:    void record(dev.caskeleton.messaging.observation.MessagingAuditEvent event);
--- and admin-runtime is allowed to depend on observability
messaging-admin-runtime -> ['messaging-core-api', 'messaging-policy', 'messaging-admin-api', 'messaging-transport-spi', 'messaging-security', 'messaging-observability']
