Scope adapter-outbound-identifier - negative-space probes
revision=a24ece9cf797f7ea647e33bf846b115208ed1ba5
generatedAt=2026-08-30T00:56:03+00:00

=== 8.1 public-surface reachability: each production type's consumers ===
--- UuidCodec
  repo-wide (excluding this leaf):
    adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/mapping/MongoBsonSnapshot.java:16:import org.bson.codecs.UuidCodec;
    adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/mapping/MongoBsonSnapshot.java:59:        CodecRegistries.fromCodecs(new UuidCodec(UuidRepresentation.STANDARD)),
    sample-portfolio/README.md:333:- 36자 canonical UUID 와 PostgreSQL native `uuid`(128비트)를 서로 변환합니다. `UuidCodec` 같은 공용
  in-leaf:
    adapter/outbound/identifier/CLAUDE.md
    adapter/outbound/identifier/README.md
    adapter/outbound/identifier/build.gradle
    adapter/outbound/identifier/src/main/java/dev/caskeleton/adapter/outbound/identifier/UuidCodec.java
    adapter/outbound/identifier/src/test/groovy/dev/caskeleton/adapter/outbound/identifier/UuidCodecSpec.groovy
--- HmacUserPrincipalPseudonymizer
  repo-wide (excluding this leaf):
    app-bootstrap/src/main/java/dev/caskeleton/bootstrap/logging/PseudonymizationConfig.java:3:import dev.caskeleton.adapter.outbound.identifier.HmacUserPrincipalPseudonymizer;
    app-bootstrap/src/main/java/dev/caskeleton/bootstrap/logging/PseudonymizationConfig.java:23:    return new HmacUserPrincipalPseudonymizer(privacySettings.saltBytes());
    sample-portfolio/src/main/java/dev/caskeleton/sample/portfolio/bootstrap/logging/SamplePseudonymizationConfig.java:3:import dev.caskeleton.adapter.outbound.identifier.HmacUserPrincipalPseudonymizer;
    sample-portfolio/src/main/java/dev/caskeleton/sample/portfolio/bootstrap/logging/SamplePseudonymizationConfig.java:23:    return new HmacUserPrincipalPseudonymizer(privacySettings.saltBytes());
  in-leaf:
    adapter/outbound/identifier/README.md
    adapter/outbound/identifier/src/main/java/dev/caskeleton/adapter/outbound/identifier/HmacUserPrincipalPseudonymizer.java
    adapter/outbound/identifier/src/test/java/dev/caskeleton/adapter/outbound/identifier/HmacUserPrincipalPseudonymizerTest.java
--- RandomUploadIdentifierFactory
  repo-wide (excluding this leaf):
    app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverServiceConfiguration.java:4:import dev.caskeleton.adapter.outbound.identifier.RandomUploadIdentifierFactory;
    app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverServiceConfiguration.java:94:    return new RandomUploadIdentifierFactory();
  in-leaf:
    adapter/outbound/identifier/src/main/java/dev/caskeleton/adapter/outbound/identifier/RandomUploadIdentifierFactory.java

=== 8.1b who calls each UuidCodec method ===
$ git grep -rn 'UuidCodec\.' -- . | grep -v '^Binary'
adapter/outbound/identifier/src/test/groovy/dev/caskeleton/adapter/outbound/identifier/UuidCodecSpec.groovy:11:        UuidCodec.normalize(input) == CANONICAL
adapter/outbound/identifier/src/test/groovy/dev/caskeleton/adapter/outbound/identifier/UuidCodecSpec.groovy:21:        UuidCodec.normalize(null) == null
adapter/outbound/identifier/src/test/groovy/dev/caskeleton/adapter/outbound/identifier/UuidCodecSpec.groovy:26:        UuidCodec.normalize("not-a-uuid")
adapter/outbound/identifier/src/test/groovy/dev/caskeleton/adapter/outbound/identifier/UuidCodecSpec.groovy:34:        def uuid = UuidCodec.toUuid(CANONICAL)
adapter/outbound/identifier/src/test/groovy/dev/caskeleton/adapter/outbound/identifier/UuidCodecSpec.groovy:37:        UuidCodec.fromUuid(uuid) == CANONICAL
exit=0

$ git grep -rn 'normalize(\|toUuid(\|fromUuid(' -- . | grep -v '^Binary' | grep -v "^adapter/outbound/identifier/" | head -20
adapter/inbound/web/src/main/java/dev/caskeleton/adapter/inbound/web/authz/RolePermissionRegistry.java:32:              resolved.put(normalize(role), permissions);
adapter/inbound/web/src/main/java/dev/caskeleton/adapter/inbound/web/authz/RolePermissionRegistry.java:44:        .map(role -> permissionsByRole.getOrDefault(normalize(role), Set.of()))
adapter/inbound/web/src/main/java/dev/caskeleton/adapter/inbound/web/authz/RolePermissionRegistry.java:49:  private static String normalize(String role) {
adapter/inbound/web/src/main/java/dev/caskeleton/adapter/inbound/web/error/ClientSafeValidationDetails.java:49:    return detail(normalize(violation.getPropertyPath()), rule);
adapter/inbound/web/src/main/java/dev/caskeleton/adapter/inbound/web/error/ClientSafeValidationDetails.java:53:    return detail(normalize(fieldError.getField()), ruleFor(fieldError.getCode()));
adapter/inbound/web/src/main/java/dev/caskeleton/adapter/inbound/web/error/ClientSafeValidationDetails.java:69:  private static String normalize(Path path) {
adapter/inbound/web/src/main/java/dev/caskeleton/adapter/inbound/web/error/ClientSafeValidationDetails.java:79:    return names.isEmpty() ? normalize(path.toString()) : String.join(".", names);
adapter/inbound/web/src/main/java/dev/caskeleton/adapter/inbound/web/error/ClientSafeValidationDetails.java:82:  private static String normalize(String rawPath) {
adapter/inbound/web/src/main/java/dev/caskeleton/adapter/inbound/web/proxy/ForwardedHeaderSanitizer.java:53:  public NormalizedForwardedHeaders normalize(String peerAddress, Map<String, String> headers) {
adapter/inbound/web/src/main/java/dev/caskeleton/adapter/inbound/web/proxy/ForwardedHeaderSanitizer.java:69:  public NormalizedForwardedHeaders normalize(InetAddress peer, Map<String, String> headers) {
adapter/inbound/web/src/main/java/dev/caskeleton/adapter/inbound/web/proxy/ForwardedHeaderSanitizer.java:70:    return normalize(peer == null ? null : peer.getHostAddress(), headers);
adapter/inbound/web/src/test/java/dev/caskeleton/adapter/inbound/web/notification/platform/callback/NotificationCallbackMvcControllerTest.java:217:        public List<NormalizedProviderEvent> normalize(VerifiedCallback callback) {
adapter/inbound/web/src/test/java/dev/caskeleton/adapter/inbound/web/proxy/ForwardedHeaderSanitizerTest.java:26:            () -> sanitizer.normalize("203.0.113.10", Map.of("X-Forwarded-Host", "evil.example")))
adapter/inbound/web/src/test/java/dev/caskeleton/adapter/inbound/web/proxy/ForwardedHeaderSanitizerTest.java:37:                sanitizer.normalize("203.0.113.10", Map.of("X-Forwarded-Host", "attacker-chosen")))
adapter/inbound/web/src/test/java/dev/caskeleton/adapter/inbound/web/proxy/ForwardedHeaderSanitizerTest.java:46:        sanitizer.normalize(
adapter/inbound/web/src/test/java/dev/caskeleton/adapter/inbound/web/proxy/ForwardedHeaderSanitizerTest.java:70:        sanitizer.normalize(
adapter/inbound/web/src/test/java/dev/caskeleton/adapter/inbound/web/proxy/ForwardedHeaderSanitizerTest.java:94:    assertThat(direct.normalize("10.1.2.3", Map.of("X-Forwarded-Host", "evil.example")))
adapter/inbound/web/src/test/java/dev/caskeleton/adapter/inbound/web/proxy/ForwardedHeaderSanitizerTest.java:102:    assertThat(sanitizer.normalize("203.0.113.10", Map.of("Accept", "application/json")))
adapter/inbound/web/src/test/java/dev/caskeleton/adapter/inbound/web/testkit/contract/CrossStackParityTest.java:66:    assertThat(normalize(reactive))
adapter/inbound/web/src/test/java/dev/caskeleton/adapter/inbound/web/testkit/contract/CrossStackParityTest.java:68:        .isEqualTo(normalize(tomcat));
exit=0

=== 8.2 UUID version: what the docs claim vs what is generated ===
$ grep -n 'UUIDv7\|RFC 9562\|uuid-creator\|f4b6a3' adapter/outbound/identifier/CLAUDE.md adapter/outbound/identifier/README.md adapter/outbound/identifier/build.gradle
adapter/outbound/identifier/CLAUDE.md:21:- `UuidCodec` — UUID handling on top of the JDK `java.util.UUID` (RFC 9562 UUIDv7):
adapter/outbound/identifier/CLAUDE.md:32:  only `:domain-core` + `com.github.f4b6a3:uuid-creator` are declared in
adapter/outbound/identifier/README.md:12:없는**(no external-system integration) 능력만 담는다: 식별자 생성/인코딩(UUIDv7), 같은 근거로 clock·
adapter/outbound/identifier/README.md:19:도메인 무관 UUID 변환 유틸. JDK `java.util.UUID`(RFC 9562 UUIDv7) 위에서 동작한다.
exit=0

$ grep -n 'randomUUID\|time-ordered' adapter/outbound/identifier/src/main/java/dev/caskeleton/adapter/outbound/identifier/RandomUploadIdentifierFactory.java
12: * strong source rather than a sequence or a timestamp. A time-ordered identifier would be the
17: * <p>{@link UUID#randomUUID()} is backed by a seeded {@code SecureRandom} and is safe to share
24:    return FileId.of(UUID.randomUUID());
29:    return UploadId.of(UUID.randomUUID());
exit=0

$ git grep -rn 'f4b6a3\|uuid-creator\|UuidCreator' -- . | grep -v '^Binary' | head
adapter/outbound/identifier/CLAUDE.md:32:  only `:domain-core` + `com.github.f4b6a3:uuid-creator` are declared in
app-bootstrap/gradle.lockfile:33:com.github.f4b6a3:uuid-creator:6.1.1=sampleFixture,testRuntimeClasspath
app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/CleanArchitectureTest.java:2461:          .haveFullyQualifiedName("com.github.f4b6a3.uuid.UuidCreator")
app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/CleanArchitectureTest.java:2465:                  + "(UUID.randomUUID / UuidCreator) — inject the domain WorkLogIdFactory port "
sample-portfolio/README.md:386:- 둘 다 `UuidCreator.getTimeOrderedEpochPlus1()` 을 씁니다 — RFC 9562 UUIDv7(time-ordered)이며 같은
sample-portfolio/README.md:388:- **여기가 샘플에서 `UuidCreator` 호출이 허용되는 유일한 곳**입니다. `no_uuid_random_in_controller`
sample-portfolio/build.gradle:52:    implementation 'com.github.f4b6a3:uuid-creator:6.1.1'
sample-portfolio/gradle.lockfile:19:com.github.f4b6a3:uuid-creator:6.1.1=compileClasspath,posterImageMigrationTestCompileClasspath,posterImageMigrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
sample-portfolio/src/main/java/dev/caskeleton/sample/portfolio/adapter/inbound/web/operation/SampleOperationStore.java:3:import com.github.f4b6a3.uuid.UuidCreator;
sample-portfolio/src/main/java/dev/caskeleton/sample/portfolio/adapter/inbound/web/operation/SampleOperationStore.java:27:    String id = UuidCreator.getTimeOrderedEpochPlus1().toString();
exit=0

=== 8.2b UuidCodec null handling across the three sibling methods ===
$ grep -n 'public static' -A 6 adapter/outbound/identifier/src/main/java/dev/caskeleton/adapter/outbound/identifier/UuidCodec.java
16:  public static String normalize(String input) {
17-    if (input == null) {
18-      return null;
19-    }
20-    return UUID.fromString(input).toString();
21-  }
22-
23:  public static UUID toUuid(String uuidString) {
24-    return UUID.fromString(uuidString);
25-  }
26-
27:  public static String fromUuid(UUID uuid) {
28-    return uuid.toString();
29-  }
30-}
exit=0

=== 8.3 declared dependencies: CLAUDE.md claim vs build.gradle vs registry ===
$ grep -n 'Allowed' -A 4 adapter/outbound/identifier/CLAUDE.md
29:## Allowed
30-
31-- `:application-core`, `:domain-core`, `:shared-contract` (Gradle matrix). Currently
32-  only `:domain-core` + `com.github.f4b6a3:uuid-creator` are declared in
33-  [build.gradle](build.gradle).
exit=0

$ grep -n 'dependencies' -A 6 adapter/outbound/identifier/build.gradle
6:dependencies {
7-    implementation project(':application-core')
8-
9-    testImplementation 'org.spockframework:spock-core:2.4-groovy-5.0'
10-}
11-
12-// Pin UTF-8 so non-ASCII (Korean) Spock spec names build on any host. See README.
exit=0

$ grep -n 'allowedProjectDependencies' -A 40 build.gradle | grep -n 'identifier' 
exit=1

$ grep -rn 'adapter-identifier\|adapter-outbound-identifier' build.gradle | head
exit=0

=== 8.3b package root: README claim vs actual ===
$ grep -n 'dev.caskeleton.adapter' adapter/outbound/identifier/README.md adapter/outbound/identifier/CLAUDE.md
adapter/outbound/identifier/README.md:3:비-IO 인프라 능력(capability) 어댑터 모듈. 패키지 루트: `dev.caskeleton.adapter.identifier`.
adapter/outbound/identifier/CLAUDE.md:11:Package root: `dev.caskeleton.adapter.outbound.identifier`.
exit=0

$ head -1 adapter/outbound/identifier/src/main/java/dev/caskeleton/adapter/outbound/identifier/UuidCodec.java
package dev.caskeleton.adapter.outbound.identifier;
exit=0

=== 8.3c Spock/Groovy variant: README claim vs build.gradle ===
$ grep -n 'Spock\|Groovy\|groovy' adapter/outbound/identifier/README.md | head
55:### Groovy / Spock (C2 테스트 형태)
56:순수 값-코덱 동작(`UuidCodec`)은 Groovy/Spock 스펙(`src/test/groovy`)으로 명세한다. core `groovy`
59:Java(`src/test/java`)로 둔다. Spock 2.4 / Groovy 4.0 variant 를 쓰며, spock-core 가 groovy.jar 를
60:transitive 로 끌어오므로 data-driven `where:` 스펙에 다른 Groovy 모듈이 필요 없다.
69:한국어(비-ASCII) Spock 스펙 메서드명은 소스를 UTF-8 로 읽어야만 컴파일·리포팅이 정상이다. 이 모듈이
exit=0

$ grep -n 'spock-core' adapter/outbound/identifier/build.gradle
9:    testImplementation 'org.spockframework:spock-core:2.4-groovy-5.0'
exit=0

=== 8.4 the guards CLAUDE.md names: do they exist? ===
$ git grep -rn 'identifier_adapter_does_not_depend_on_other_adapters_or_bootstrap' -- . | grep -v '^Binary'
adapter/outbound/identifier/CLAUDE.md:38:  `identifier_adapter_does_not_depend_on_other_adapters_or_bootstrap`;
exit=0

$ ls ../.claude/hooks/ 2>&1
ls: cannot access '../.claude/hooks/': No such file or directory
exit=2

$ grep -n 'G4' ../.claude/hooks/ca_import_gate.py 2>/dev/null | head
exit=0

=== 8.4b UUID handling elsewhere: is UuidCodec the single path? ===
$ git grep -rln 'UUID.fromString' -- . | grep -v '^Binary' | grep -v "^adapter/outbound/identifier/" | head -20
adapter/inbound/graphql/src/main/java/dev/caskeleton/adapter/inbound/graphql/scalar/UuidScalar.java
adapter/inbound/graphql/src/test/java/dev/caskeleton/adapter/inbound/graphql/scalar/GraphQlScalarContractTest.java
adapter/inbound/web/src/test/java/dev/caskeleton/adapter/inbound/web/advanced/codec/WebCodecMapperTest.java
adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/platform/dispatch/JacksonRoutingPlanCodec.java
adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/postgresql/PostgreSqlIdempotencyClaimRepository.java
adapter/outbound/persistence-jpa/src/postgresqlIntegrationTest/java/dev/caskeleton/adapter/outbound/persistence/readiness/PostgreSqlInboxCutoffIntegrationTest.java
adapter/outbound/persistence-jpa/src/postgresqlIntegrationTest/java/dev/caskeleton/adapter/outbound/persistence/readiness/PostgreSqlNotificationIdempotencyRaceIntegrationTest.java
adapter/outbound/persistence-jpa/src/test/java/dev/caskeleton/adapter/outbound/persistence/api/query/SignedJsonCursorCodecTest.java
adapter/outbound/persistence-mongo/src/main/java/dev/caskeleton/adapter/outbound/mongo/query/pagination/MongoCursorValueCodec.java
app-bootstrap/src/test/java/dev/caskeleton/bootstrap/integration/fileserver/FileserverRoundTripContractTest.java
application-core/src/main/java/dev/caskeleton/application/fileserver/api/FileId.java
application-core/src/main/java/dev/caskeleton/application/fileserver/api/UploadId.java
application-core/src/test/java/dev/caskeleton/application/fileserver/api/ValueObjectTest.java
application-core/src/test/java/dev/caskeleton/application/notification/platform/dispatch/PlatformFakes.java
application-core/src/test/java/dev/caskeleton/application/notification/platform/dispatch/SyntheticEventFingerprintTest.java
messaging/messaging-admin-runtime/src/test/java/dev/caskeleton/messaging/admin/runtime/ApprovalForgeryTest.java
messaging/messaging-admin-runtime/src/test/java/dev/caskeleton/messaging/admin/runtime/ApprovedPlanExecutionTest.java
messaging/messaging-cloudevents/src/main/java/dev/caskeleton/messaging/cloudevents/DefaultCloudEventMapper.java
messaging/messaging-cloudevents/src/test/java/dev/caskeleton/messaging/cloudevents/CloudEventMappingTest.java
messaging/messaging-core-api/src/test/java/dev/caskeleton/messaging/api/CoreValueTypesTest.java
exit=0

$ git grep -rn 'columnDefinition = "uuid"\|@JdbcTypeCode\|SqlTypes.UUID' -- adapter/outbound/persistence-jpa | head -10
adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/fileserver/entity/CleanupItemEntity.java:24:  @JdbcTypeCode(SqlTypes.UUID)
adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/fileserver/entity/CleanupItemEntity.java:28:  @JdbcTypeCode(SqlTypes.UUID)
adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/fileserver/entity/CleanupItemEntity.java:42:  @JdbcTypeCode(SqlTypes.UUID)
adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/fileserver/entity/CleanupItemEntity.java:75:  @JdbcTypeCode(SqlTypes.UUID)
adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/fileserver/entity/FileEntity.java:26:  @JdbcTypeCode(SqlTypes.UUID)
adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/fileserver/entity/FileEntity.java:56:  @JdbcTypeCode(SqlTypes.CHAR)
adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/fileserver/entity/QuotaReservationEntity.java:24:  @JdbcTypeCode(SqlTypes.UUID)
adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/fileserver/entity/RecoveryItemEntity.java:25:  @JdbcTypeCode(SqlTypes.UUID)
adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/fileserver/entity/RecoveryItemEntity.java:29:  @JdbcTypeCode(SqlTypes.UUID)
adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/fileserver/entity/UploadSessionEntity.java:30:  @JdbcTypeCode(SqlTypes.UUID)
exit=0


=== 8.4c what actually enforces this leaf's boundary, if not the named rule ===
$ grep -rn 'allowedProjectDependencies' build.gradle | head
1416:    Map<String, Set<String>> allowedProjectDependencies = registry.modules.collectEntries { module ->
1427:        Set<String> governedModules = allowedProjectDependencies.keySet()
1445:        allowedProjectDependencies.each { moduleName, allowed ->
exit=0

$ grep -rn 'allowedProjectDependencies' -A 60 build.gradle | grep -iE 'identifier|adapter-outbound' | head
exit=0

$ git grep -rn 'identifier' -- app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/CleanArchitectureTest.java | head -20
app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/CleanArchitectureTest.java:33:import dev.caskeleton.domain.identifier.ResourceId;
app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/CleanArchitectureTest.java:948:   * EXCLUDING the {@code ..adapter.outbound.identifier..} sub-package: UUIDv7 id/event-id factories
app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/CleanArchitectureTest.java:981:   * ..adapter.outbound.identifier..} (UUIDv7 id/event-id generation & codec). Both nest under
app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/CleanArchitectureTest.java:997:        || packageName.contains(".adapter.outbound.identifier");
app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/CleanArchitectureTest.java:1064:              "..adapter.outbound.identifier..",
app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/CleanArchitectureTest.java:1258:  // NOTE: adapter.outbound.identifier AND adapter.outbound.persistence are both nested under
app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/CleanArchitectureTest.java:1263:  // above. Without this, an intra-identifier or intra-persistence reference (a class referencing a
app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/CleanArchitectureTest.java:1272:          .resideOutsideOfPackage("..adapter.outbound.identifier..")
app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/CleanArchitectureTest.java:1284:              "..adapter.outbound.identifier..",
app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/CleanArchitectureTest.java:1292:          .resideInAPackage("..adapter.outbound.identifier..")
app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/CleanArchitectureTest.java:1308:                                  "..adapter.outbound.identifier.."))))
app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/CleanArchitectureTest.java:1310:              "adapter:outbound:identifier is a non-IO driven adapter (UUIDv7 generation/codec) —"
app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/CleanArchitectureTest.java:1312:                  + " (feature-resource-identifier-contract §4 taxonomy).")
app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/CleanArchitectureTest.java:1367:              "outbound adapters (persistence / identifier / cache / httpclient / messaging / "
app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/CleanArchitectureTest.java:2432:  // ---- feature-resource-identifier-contract D17 (decision SSOT: resource-identifier branch) ----
app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/CleanArchitectureTest.java:2447:                  + " (feature-resource-identifier-contract D17). JPA @Id UUID columns in"
app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/CleanArchitectureTest.java:2464:                  + "(use cases / services) must not generate resource identifiers directly "
app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/CleanArchitectureTest.java:2466:                  + "instead (feature-resource-identifier-contract D5/D17). Scope follows the "
app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/CleanArchitectureTest.java:2481:                  + "seed an identifier (feature-resource-identifier-contract D9/D17).")
app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/CleanArchitectureTest.java:2495:                  + "back to varchar(255) (feature-resource-identifier-contract D17).")
exit=0

$ ls -a ../ | head -20; ls ../.claude 2>&1
.
..
.agents
.claude
.codex
.git
.gitattributes
.github
.gitignore
.superpowers
.tool-versions
.trivyignore.yaml
.vscode
AGENTS.md
CLAUDE.md
README.md
docker-compose.dev.yml
docker-compose.infra.yml
docker-compose.local.yml
docker-compose.prod-smoke.yml
settings.local.json
exit=0

=== 8.4d does anything test UuidCodec's D3/D10 claim outside its own spec? ===
$ git grep -rn 'D3\b\|D10\b' -- 'adapter/outbound/identifier' ../docs 2>/dev/null | head -12
../docs/adr/ADR-MONGO-001-platform-boundary.md:28:| D3 Explicit Mongo capability | Native BSON, time series, search/vector, CSFLE/QE, shard-aware operations | Separate capability client |
../docs/adr/ADR-MONGO-001-platform-boundary.md:36:3. **D3 is not a raw-client escape.** Every call passes a fixed admission order: capability registered
../docs/mongodb/repository-adaptation.md:103:- D1 / D2 / D3 / D4 exposure planes and the ordered D3 admission sequence (§5).
../docs/mongodb/repository-adaptation.md:104:- Stable API V1 with `apiStrict=true` on the D1/D2 client generation; D3/D4 on separate generations.
../docs/mongodb/security-observability.md:62:The native capability gateway (D3) refuses any admin-category command, so there is no path from the
../docs/mongodb/support-matrix.md:55:| D3 Explicit Mongo capability | Not strict | Native BSON, time series, search/vector, CSFLE/QE, shard-aware operations — each behind a registered capability. |
../docs/mongodb/support-matrix.md:58:D3 is not a raw-client escape. Every call passes capability registration → database profile →
../docs/superpowers/plans/2026-08-11-mongodb-document-persistence-platform-implementation-plan.md:7:**Architecture:** `mongodb-core-api`가 framework-free 의미론을 소유하고 Spring Data·imperative·reactive·transaction·query·aggregation·index-schema·change-stream 모듈이 이를 구현한다. 도메인은 Document와 Repository를 직접 소유하며 플랫폼은 범용 CRUD Repository를 만들지 않는다. D1/D2는 Stable API V1 strict client, D3는 승인된 Capability client, D4는 Runtime과 분리된 Admin client를 사용한다.
../docs/superpowers/plans/2026-08-11-mongodb-document-persistence-platform-implementation-plan.md:22:- D3 Capability client와 D4 Admin client는 별도 권한·설정·모듈이다.
../docs/superpowers/plans/2026-08-11-mongodb-document-persistence-platform-implementation-plan.md:2948:### Task 38: D3 Native Capability Gateway Guardrail 구현
../docs/superpowers/plans/2026-08-15-wave2-module-on-path.md:389:### Task D3: Mode-scoped worker lifecycle (NTF-INT-003)
../docs/superpowers/plans/evidence/2026-08-15-wave2-decisions.md:748:## D3 — INGEST_ONLY was delivering (NTF-INT-003)
exit=0


=== 8.4e the two guards CLAUDE.md names, resolved accurately ===
(1) ArchUnit rule name
$ git grep -rn 'identifier_adapter_does_not_depend_on_other_adapters_or_bootstrap' -- . | grep -v '^Binary'
adapter/outbound/identifier/CLAUDE.md:38:  `identifier_adapter_does_not_depend_on_other_adapters_or_bootstrap`;
exit=0

$ sed -n '1258,1315p' app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/CleanArchitectureTest.java
  // NOTE: adapter.outbound.identifier AND adapter.outbound.persistence are both nested under
  // adapter.outbound (sibling adapters, not top-level packages), so the rules below exclude each
  // one's own package from the side that would otherwise match it against itself — mirroring the
  // resideOutsideOfPackage sibling-exclusion style already used for
  // PERSISTENCE_RDBMS_STAYS_VENDOR_NEUTRAL / PRODUCTION_CODE_DOES_NOT_DEPEND_ON_TEST_FIXTURES
  // above. Without this, an intra-identifier or intra-persistence reference (a class referencing a
  // class in its own nested adapter) would spuriously violate one of the rules.

  @ArchTest
  static final ArchRule OUTBOUND_ADAPTER_DOES_NOT_DEPEND_ON_WEB_OR_PERSISTENCE_ADAPTERS =
      noClasses()
          .that()
          .resideInAPackage("..adapter.outbound..")
          .and()
          .resideOutsideOfPackage("..adapter.outbound.identifier..")
          // persistence-jpa is itself a nested outbound adapter; exclude its own package from the
          // selector so its intra-references (which land in the ..adapter.outbound.persistence..
          // forbidden target below) do not self-overlap. Generic outbound adapters (messaging,
          // cache, http, ...) are still forbidden from depending on persistence.
          .and()
          .resideOutsideOfPackage("..adapter.outbound.persistence..")
          .should()
          .dependOnClassesThat()
          .resideInAnyPackage(
              "..adapter.inbound.web..",
              "..adapter.outbound.persistence..",
              "..adapter.outbound.identifier..",
              "..bootstrap..")
          .allowEmptyShould(true);

  @ArchTest
  static final ArchRule IDENTIFIER_ADAPTER_DOES_NOT_DEPEND_ON_OTHER_ADAPTERS_OR_BOOTSTRAP =
      noClasses()
          .that()
          .resideInAPackage("..adapter.outbound.identifier..")
          .should()
          .dependOnClassesThat(
              JavaClass.Predicates.resideInAnyPackage(
                      "..adapter.inbound.web..",
                      "..adapter.outbound.persistence..",
                      "..bootstrap..",
                      "org.springframework.data.repository..",
                      "org.springframework.data.jpa.repository..",
                      "jakarta.persistence..",
                      "javax.persistence..",
                      "org.hibernate..")
                  .or(
                      JavaClass.Predicates.resideInAPackage("..adapter.outbound..")
                          .and(
                              JavaClass.Predicates.resideOutsideOfPackage(
                                  "..adapter.outbound.identifier.."))))
          .as(
              "adapter:outbound:identifier is a non-IO driven adapter (UUIDv7 generation/codec) —"
                  + " it must not reach into sibling adapters, persistence, or the composition root"
                  + " (feature-resource-identifier-contract §4 taxonomy).")
          .allowEmptyShould(true);

  // ---- Task 7: explicit inbound/outbound adapter topology (hexagonal driving/driven split) ----
exit=0

(2) the import-gate hook
$ ls -a ../.claude/
.
..
settings.local.json
exit=0

$ find ../.claude -name '*import*' -o -name '*gate*' | head
exit=0

=== 8.4f the decision document CLAUDE.md/README cite ===
$ grep -rn 'feature-resource-identifier-contract' -- . ../docs ../.agents 2>/dev/null | grep -v '^Binary' | sed 's/:.*contract/: ...contract/' | sort -u | head -12
./adapter/outbound/identifier/CLAUDE.md: ...contract §4 taxonomy).
./app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/CleanArchitectureTest.java: ...contract D17 (decision SSOT: resource-identifier branch) ----
./app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/CleanArchitectureTest.java: ...contract D17)"));
./app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/CleanArchitectureTest.java: ...contract D17). JPA @Id UUID columns in"
./app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/CleanArchitectureTest.java: ...contract D17).")
./app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/CleanArchitectureTest.java: ...contract D5/D17). Scope follows the "
./app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/CleanArchitectureTest.java: ...contract D9/D17).")
./app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/CleanArchitectureTest.java: ...contract §4 taxonomy).")
./sample-portfolio/src/test/java/dev/caskeleton/sample/portfolio/fixtures/SamplePortfolioFixture.java: ...contract §8/D19).
exit=0

$ find .. -iname '*resource-identifier*' -not -path '*/build/*' -not -path '*/.git/*' | head
exit=0

=== 8.4g how README's allowedProjectDependencies key compares to the real mechanism ===
$ sed -n '1410,1450p' build.gradle
    // This task used to re-parse the same JSON and re-implement four rules the settings plugin
    // enforces first: file present, module list non-empty, ids unique, allowed-dependency ids
    // resolvable. Two implementations of one rule are two definitions of "valid", and the
    // disagreement only becomes visible on the day one of them is wrong. What is left here is what
    // this task actually owns — comparing the declared edges against the ones Gradle resolved.
    def registry = gradle.moduleRegistry
    Map<String, Set<String>> allowedProjectDependencies = registry.modules.collectEntries { module ->
        String moduleName = module.gradlePath.replaceFirst('^:', '')
        Set<String> allowed = module.allowedDependencies.collect { dependencyId ->
            registry.byId(dependencyId).gradlePath.replaceFirst('^:', '')
        }.toSet()
        [(moduleName): allowed]
    }

    doLast {
        Set<String> declaredModules = subprojects.findAll { it.childProjects.isEmpty() }
                .collect { it.path.replaceFirst('^:', '') }.toSet()
        Set<String> governedModules = allowedProjectDependencies.keySet()
        Set<String> missingFromBuild = governedModules - declaredModules
        Set<String> missingFromPolicy = declaredModules - governedModules

        if (!missingFromBuild.isEmpty()) {
            throw new GradleException(
                    "Clean Architecture dependency policy references missing Gradle modules ${missingFromBuild}. " +
                            "Declared modules are ${declaredModules}."
            )
        }

        if (!missingFromPolicy.isEmpty()) {
            throw new GradleException(
                    "Gradle modules ${missingFromPolicy} are not covered by verifyCleanArchitectureDependencies. " +
                            "Add an explicit dependency policy before using them."
            )
        }

        allowedProjectDependencies.each { moduleName, allowed ->
            Project module = rootProject.project(":${moduleName}")
            Set<String> actual = ['api', 'implementation', 'compileOnly', 'runtimeOnly']
                    .collect { configurationName -> module.configurations.findByName(configurationName) }
                    .findAll { it != null }
                    .collectMany { configuration ->
exit=0

=== EXECUTION PROBE (temporary test class, added / run / removed) ===
  src/test/java/.../IdProbe.java  (:adapter:outbound:identifier:test)

PROBE normalize("0190bd6e-7c3e-7abc-8def-0123456789ab") -> "0190bd6e-7c3e-7abc-8def-0123456789ab"
PROBE normalize("0190BD6E-7C3E-7ABC-8DEF-0123456789AB") -> "0190bd6e-7c3e-7abc-8def-0123456789ab"
PROBE normalize("1-1-1-1-1")  -> "00000001-0001-0001-0001-000000000001"
PROBE normalize("0-0-0-0-0")  -> "00000000-0000-0000-0000-000000000000"
PROBE normalize("1-2-3-4-5")  -> "00000001-0002-0003-0004-000000000005"
PROBE normalize("0190bd6e7c3e7abc8def0123456789ab")      -> IllegalArgumentException
PROBE normalize("0190bd6e-...-0123456789ab-extra")        -> IllegalArgumentException
PROBE normalize("  0190bd6e-...-0123456789ab  ")          -> IllegalArgumentException
PROBE normalize("not-a-uuid")                             -> IllegalArgumentException

PROBE normalize(null) -> null
PROBE toUuid(null)    -> NullPointerException
PROBE fromUuid(null)  -> NullPointerException
PROBE toUuid("not-a-uuid") -> IllegalArgumentException

PROBE newFileId   version=4 variant=2
PROBE newUploadId version=4 variant=2
PROBE UUID.randomUUID() version=4   (RFC 9562 UUIDv7 would report version=7)

Reading: UUID.fromString's non-fast path accepts five dash-separated hex groups of any
length, so normalize() accepts strings that are not canonical UUIDs and rewrites them into
canonical-looking ones. The Javadoc and README say it accepts a canonical UUID and raises
IllegalArgumentException on a malformed one. The Spock spec's single rejection case,
"not-a-uuid", does not have the dash shape that reaches the lenient path.

=== source tree after probe removal ===
$ git status --short | wc -l
0
exit=0

$ git status --short
exit=0

