adapter-inbound-web module inventory / denominator
revision=a24ece9cf797f7ea647e33bf846b115208ed1ba5
generatedAt=2026-08-30T07:06:20+00:00

=== TRACKED FILE COUNTS ===
$ git ls-files adapter/inbound/web | wc -l
638
exit=0

$ git ls-files adapter/inbound/web/src/main | wc -l
400
exit=0

$ git ls-files adapter/inbound/web/src/test | wc -l
150
exit=0

$ git ls-files adapter/inbound/web | grep -v '^adapter/inbound/web/src/' 
adapter/inbound/web/CLAUDE.md
adapter/inbound/web/README.md
adapter/inbound/web/build.gradle
adapter/inbound/web/gradle.lockfile
exit=0

=== MAIN JAVA LOC ===
$ git ls-files 'adapter/inbound/web/src/main/**/*.java' | xargs wc -l | tail -1
  27473 total
exit=0

$ git ls-files 'adapter/inbound/web/src/test/**/*.java' | xargs wc -l | tail -1
 18319 total
exit=0

$ git ls-files adapter/inbound/web/src/main | grep -v '\.java$'
adapter/inbound/web/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
adapter/inbound/web/src/main/resources/META-INF/web/problem-catalog.yaml
adapter/inbound/web/src/main/resources/META-INF/web/wire-type-manifest.yaml
exit=0

$ git ls-files adapter/inbound/web/src/test | grep -v '\.java$'
adapter/inbound/web/src/test/java/dev/caskeleton/adapter/inbound/web/contract/.gitkeep
adapter/inbound/web/src/test/java/dev/caskeleton/adapter/inbound/web/mvc/.gitkeep
adapter/inbound/web/src/test/resources/application-web-contract.yaml
exit=0

=== MAIN PACKAGE TREE (file counts) ===
$ git ls-files 'adapter/inbound/web/src/main/**/*.java' | sed 's|.*/web/||;s|/[^/]*$||' | sort | uniq -c | sort -rn
     19 pagination
     18 ratelimit
     16 error
     13 advanced/stream
     13 advanced/patch
     12 observability
     12 http
     11 security
     11 operation
     11 auth
     10 fileserver/reactive
     10 conditional
      9 operationasync
      9 idempotency
      9 core
      7 versioning
      7 openapi
      7 notification/platform/submission
      7 budget
      7 advanced/webflux
      7 advanced/stream/encoding
      6 evidence
      6 advanced/codec
      5 notification/platform/callback
      5 fileserver/tus
      5 fileserver/problem
      5 fileserver/dto
      5 fileserver/draft12
      5 authz
      5 advanced/stream/replay
      5 advanced/openapi
      5 advanced/mvc
      5 admission
      4 validation
      4 proxy
      4 notification/platform/callback/reactive
      4 mvc/budget
      4 json
      4 fileserver/nginx
      4 fileserver/config
      4 contract
      4 cache
      4 advanced/functional
      4 admin/route
      3 settings
      3 moduleboundary
      3 fileserver/mapper
      3 fileserver/http
      3 fileserver/admin
      3 config
      3 advanced/virtualthread
      3 advanced/ratelimit
      3 advanced/blockingbridge
      2 webflux/guard
      2 webflux/context
      2 webflux/budget
      2 webflux/autoconfigure
      2 mvc/idempotency
      2 mvc/filter
      2 mvc/context
      2 mvc/autoconfigure
      2 filter
      2 fileserver/controller
      2 cursor
      2 advanced/release
      2 advanced
      2 admin/platform
      1 webflux/throttle
      1 webflux/operation
      1 webflux/idempotency
      1 webflux/error
      1 mvc/throttle
      1 mvc/operation
      1 mvc/error
      1 fileserver/security
      1 fileserver/lifecycle
      1 envelope
      1 controller
exit=0

=== TEST PACKAGE TREE ===
$ git ls-files 'adapter/inbound/web/src/test/**/*.java' | sed 's|.*/web/||;s|/[^/]*$||' | sort | uniq -c | sort -rn
     11 auth
      8 fileserver/testkit
      7 ratelimit
      7 error
      5 pagination
      5 observability
      4 idempotency
      4 authz
      3 settings
      3 moduleboundary
      3 http
      3 fileserver/reactive
      3 conditional
      3 advanced/release
      3 advanced/codec
      2 versioning
      2 testkit/contract
      2 security
      2 operationasync
      2 openapi
      2 notification/platform/callback
      2 mvc/filter
      2 fixtures/badcontroller
      2 filter
      2 fileserver/nginx
      2 fileserver/controller
      2 envelope
      2 core
      2 advanced/stream/replay
      2 advanced/stream
      2 advanced/patch
      2 advanced/openapi
      2 advanced/mvc
      1 webflux/idempotency
      1 webflux/guard
      1 webflux/context
      1 webflux/autoconfigure
      1 validation
      1 testkit/throttle
      1 testkit/performance
      1 testkit/order
      1 testkit/operation
      1 testkit/mvc
      1 testkit/fault
      1 testkit/budget
      1 testkit/arch
      1 proxy
      1 operation
      1 mvc/idempotency
      1 mvc/autoconfigure
      1 json
      1 fileserver/tus
      1 fileserver/security
      1 fileserver/problem
      1 fileserver/http
      1 fileserver/draft12
      1 fileserver/config
      1 fileserver/admin
      1 evidence
      1 cursor
      1 contract
      1 config
      1 cache
      1 budget
      1 advanced/webflux
      1 advanced/virtualthread
      1 advanced/stream/encoding
      1 advanced/ratelimit
      1 advanced/functional
      1 advanced/blockingbridge
      1 admin/route
      1 admin/platform
exit=0

=== BUILD ===
$ cat adapter/inbound/web/build.gradle
// The inbound HTTP API execution platform design models itself as 23 Stable Gradle modules under
// modules/web. This repository's fail-closed module registry outranks that layout, so those modules
// are packages here and WebModuleBoundaryTest enforces the design's module dependency table. The
// full mapping, and the three other places the design's assumptions were adapted, are in
// docs/web/repository-adaptation.md.

// HTTP / web adapters. Depends on application and shared operational contracts.
dependencies {
    implementation project(':application-core')
    implementation project(':shared-contract')
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-security'
    implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server'
    implementation 'org.springframework.boot:spring-boot-starter-validation'
    implementation 'org.springframework.session:spring-session-core'
    annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
    implementation('org.openapitools:jackson-databind-nullable:0.2.6') {
        exclude group: 'com.fasterxml.jackson.core', module: 'jackson-databind'
    }
    // feature-api-contract-baseline D10: OpenAPI producer. springdoc exposes the
    // running app's machine-readable contract at /v3/api-docs (OAS 3.1, generated —
    // never a hand-maintained stale schema). The release-blocking drift gate is
    // owned by feature-contract-verification-test-suite (planned).
    implementation 'org.springdoc:springdoc-openapi-starter-webmvc-api:3.0.0'
    // The two Advanced representations, compile-only on purpose. They were `implementation` first,
    // on the reasoning that a missing backend would surface as a NoClassDefFoundError at the first
    // request that negotiated one. That reasoning was wrong about what the jars do: Spring Boot's
    // Jackson auto-configuration registers an `xmlMapper` and a `cborMapper` the moment each
    // backend is on the runtime classpath, and Spring registers a message converter for XML with
    // it. So every deployment got three ObjectMapper beans — which broke `@Autowired ObjectMapper`
    // in the composition root with an ambiguity — and, worse, silently began parsing
    // `application/xml` request bodies. An Advanced capability that is off by default had turned
    // XML deserialization on for everybody, which is the opposite of what the flag promises and an
    // XXE surface nobody chose.
    //
    // Compile-only keeps the factories compiling and their tests running while leaving the runtime
    // classpath to the deployment that enables the capability. `WebRepresentation.available()` is
    // what turns the absent backend into a sentence instead of a NoClassDefFoundError.
    compileOnly 'tools.jackson.dataformat:jackson-dataformat-cbor'
    compileOnly 'tools.jackson.dataformat:jackson-dataformat-xml'
    testImplementation 'tools.jackson.dataformat:jackson-dataformat-cbor'
    testImplementation 'tools.jackson.dataformat:jackson-dataformat-xml'
    // Fileserver reactive transport. Only the WebFlux framework and Reactor core are declared —
    // deliberately not spring-boot-starter-webflux, which would put a second embedded server
    // (reactor-netty) on the runtime classpath. DispatcherServlet stays present, so Spring Boot's
    // WebApplicationType deduction keeps resolving SERVLET; the reactive handlers are wired only
    // when the fileserver reactive profile is selected.
    implementation 'org.springframework:spring-webflux'
    implementation 'io.projectreactor:reactor-core'
    testImplementation 'org.springframework.security:spring-security-test'
    testImplementation 'io.projectreactor:reactor-test'
}

tasks.named('test') {
    useJUnitPlatform {
        excludeTags 'security-boundary'
        // The parity gate compares recordings written by three lanes. In `test` alone only one of
        // them exists, and a gate that fails because the others have not run yet is a gate people
        // learn to ignore. It runs from `webCrossStackParityTest`, which depends on all three.
        excludeTags 'web-parity'
    }
}

// The web platform's reusable ArchUnit rules ship in their own source set, consumed by this leaf's
// tests and by the composition root. A rule pack that only its own fixture tests import is verified
// as library code and applied to nothing — the shape the JPA testkit had to be corrected out of.
strictTestLanes {
    sourceSet('testkit') { compilesAgainst 'main' }
    // The Jetty compatibility lane is its own source set because it needs a different embedded
    // server on the classpath. Two servers in one source set means Spring Boot picks one and the
    // "Jetty" lane silently runs on Tomcat — a compatibility matrix that certifies the same
    // container twice.
    sourceSet('jettyCompatTest') {
        compilesAgainst 'main', 'testkit'
        inherits 'implementation'
    }
    // Reactor Netty is the Stable WebFlux server baseline and it cannot share a source set with
    // Tomcat: Spring Boot deduces one application type from the classpath, so with both servers
    // present the reactive gate would start a servlet container and certify nothing reactive.
    // Nothing is inherited: the leaf's own `implementation` carries spring-boot-starter-web, and
    // inheriting it would put Tomcat back on this lane's classpath. Boot then deduces a servlet
    // application, starts a servlet container, and the reactive gate certifies the servlet stack.
    // The Nginx lane runs the platform behind a real reverse proxy in a container. Its own source
    // set because it is the only lane that needs Docker: folding it into `test` would make every
    // developer's `check` depend on a container runtime, and the usual outcome of that is an
    // @Disabled that nobody notices has been there for months.
    sourceSet('nginxProxyTest') {
        compilesAgainst 'main', 'testkit'
        inherits 'implementation'
    }
    sourceSet('webfluxContractTest') {
        compilesAgainst 'main', 'testkit'
        // Inherits nothing. The default is to extend `testImplementation`, which extends the leaf's
        // own `implementation` and therefore carries spring-boot-starter-web — and with Tomcat on
        // the classpath Boot deduces a servlet application, starts a servlet container, and the
        // reactive gate certifies the servlet stack while reporting itself green.
        inherits()
    }
}

testkitPublisher {
    consumedBy 'test'
    publishAs 'webTestkit'
}

// ArchUnit is declared after the testkit source set exists, because `testkitImplementation` is
// created by that declaration.
dependencies {
    testkitImplementation 'com.tngtech.archunit:archunit-junit5:1.3.0'
    testImplementation 'com.tngtech.archunit:archunit-junit5:1.3.0'

    // Jetty replaces Tomcat for this lane only. The exclusion is what makes the lane mean
    // something: with both on the classpath Boot starts Tomcat and the lane certifies nothing.
    jettyCompatTestImplementation('org.springframework.boot:spring-boot-starter-jetty')
    jettyCompatTestImplementation('org.springframework.boot:spring-boot-starter-test') {
        exclude group: 'org.springframework.boot', module: 'spring-boot-starter-tomcat'
    }
    jettyCompatTestImplementation('org.springframework.boot:spring-boot-starter-web') {
        exclude group: 'org.springframework.boot', module: 'spring-boot-starter-tomcat'
    }
    jettyCompatTestImplementation 'org.springframework.boot:spring-boot-starter-security'
    jettyCompatTestImplementation 'org.springframework.boot:spring-boot-starter-validation'
    // The root build adds the launcher to `test` only; a custom lane has to say so itself, or the
    // executor starts and finds no JUnit Platform.
    jettyCompatTestRuntimeOnly 'org.junit.platform:junit-platform-launcher'

    // The reactive lane: reactor-netty in, Tomcat out. The exclusion is what makes the lane mean
    // something — Boot picks the servlet stack when both are present.
    webfluxContractTestImplementation('org.springframework.boot:spring-boot-starter-webflux') {
        exclude group: 'org.springframework.boot', module: 'spring-boot-starter-tomcat'
    }
    webfluxContractTestImplementation('org.springframework.boot:spring-boot-starter-test') {
        exclude group: 'org.springframework.boot', module: 'spring-boot-starter-tomcat'
    }
    webfluxContractTestImplementation 'org.springframework.boot:spring-boot-starter-validation'
    webfluxContractTestImplementation 'io.projectreactor:reactor-test'
    // Explicit because this lane inherits nothing: the platform's auto-configuration references
    // ObjectMapper, and without Jackson the condition evaluation fails before a server starts.
    webfluxContractTestImplementation 'org.springframework.boot:spring-boot-starter-jackson'
    webfluxContractTestImplementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310'
    webfluxContractTestImplementation project(':application-core')
    webfluxContractTestImplementation project(':shared-contract')
    webfluxContractTestRuntimeOnly 'org.junit.platform:junit-platform-launcher'

    // The Nginx lane. Testcontainers starts the proxy; the application runs in this JVM on a
    // random port and the container reaches it through the host gateway.
    nginxProxyTestImplementation 'org.springframework.boot:spring-boot-starter-test'
    nginxProxyTestImplementation 'org.springframework.boot:spring-boot-starter-web'
    nginxProxyTestImplementation 'org.springframework.boot:spring-boot-starter-security'
    nginxProxyTestImplementation 'org.springframework.boot:spring-boot-starter-validation'
    nginxProxyTestImplementation 'org.testcontainers:testcontainers'
    nginxProxyTestImplementation 'org.testcontainers:testcontainers-junit-jupiter'
    nginxProxyTestRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

// The lane task. A release compatibility gate that is not wired to a task is a document.
tasks.register('webFluxContractTest', Test) {
    group = 'verification'
    description = 'Runs the Stable HTTP contract against a real Reactor Netty.'
    testClassesDirs = sourceSets.webfluxContractTest.output.classesDirs
    classpath = sourceSets.webfluxContractTest.runtimeClasspath
    useJUnitPlatform()
    failOnNoDiscoveredTests = true
    outputs.upToDateWhen { false }
    jvmArgs '-Duser.timezone=UTC'
}

// Docker-gated, and it says so rather than skipping. A lane that quietly passes when the container
// runtime is missing is a lane that has been certifying nothing since whenever Docker last broke.
tasks.register('webNginxProxyTest', Test) {
    group = 'verification'
    description = 'Runs the proxy, prefix and spoofing contract behind a real Nginx.'
    testClassesDirs = sourceSets.nginxProxyTest.output.classesDirs
    classpath = sourceSets.nginxProxyTest.runtimeClasspath
    useJUnitPlatform()
    failOnNoDiscoveredTests = true
    outputs.upToDateWhen { false }
    jvmArgs '-Duser.timezone=UTC'
}

// The cross-stack gate. It depends on every recording lane rather than tolerating a missing one:
// a parity check that compares whatever happens to be present would report agreement across a
// matrix with a hole in it.
tasks.register('webCrossStackParityTest', Test) {
    group = 'verification'
    description = 'Compares the wire contract recorded by Tomcat, Jetty and Reactor Netty.'
    testClassesDirs = sourceSets.test.output.classesDirs
    classpath = sourceSets.test.runtimeClasspath
    useJUnitPlatform {
        includeTags 'web-parity'
    }
    failOnNoDiscoveredTests = true
    outputs.upToDateWhen { false }
    jvmArgs '-Duser.timezone=UTC'
    dependsOn 'test', 'webJettyCompatTest', 'webFluxContractTest'
}

// The Advanced lane. Every capability is off unless a deployment names it, so none of them is
// exercised by anything a production deployment runs — which makes a lane that runs them all the
// only place a break is noticed before whoever enables it notices.
//
// They also run inside `test`, deliberately. They are ordinary unit tests, and excluding them from
// the PR gate to make this lane look meaningful would mean the PR gate stopped covering a fifth of
// the leaf.
tasks.register('webAdvancedTest', Test) {
    group = 'verification'
    description = 'Runs every web Advanced capability contract.'
    testClassesDirs = sourceSets.test.output.classesDirs
    classpath = sourceSets.test.runtimeClasspath
    useJUnitPlatform {
        includeTags 'web-advanced'
    }
    failOnNoDiscoveredTests = true
    outputs.upToDateWhen { false }
    jvmArgs '-Duser.timezone=UTC'
}

tasks.register('webJettyCompatTest', Test) {
    group = 'verification'
    description = 'Runs the Stable HTTP contract against a real Jetty instead of Tomcat.'
    testClassesDirs = sourceSets.jettyCompatTest.output.classesDirs
    classpath = sourceSets.jettyCompatTest.runtimeClasspath
    useJUnitPlatform()
    failOnNoDiscoveredTests = true
    outputs.upToDateWhen { false }
    jvmArgs '-Duser.timezone=UTC'
}

strictTestLanes {
    lane('jpaPersistenceRedactionContractTest') {
        description = 'Runs the exact persistence error log/trace redaction contract used by JPA evidence.'
        // Named rather than tagged. This lane is JPA evidence's proof that a database failure never
        // reaches a log line or a span, and it must stay exactly these two contracts — a tag would
        // let a later test opt itself in and change what the evidence covers.
        requires(
                'dev.caskeleton.adapter.inbound.web.error.GlobalExceptionHandlerTest.persistenceFailureObservabilityDoesNotCarryRawDatabaseDetails',
                'dev.caskeleton.adapter.inbound.web.error.SpanErrorRecorderHookTest.persistenceFailureHandlerRecordsSanitizedExceptionWithClassifiedCode')
    }

    lane('webSecurityBoundaryTest') {
        tag = 'security-boundary'
        description = 'Runs hermetic JWT/JWKS and CORS filter-boundary contracts with no skips.'
        customize = { test ->
            test.shouldRunAfter test.project.tasks.named('test')
            test.jvmArgs '-Duser.timezone=UTC'
            test.afterSuite { descriptor, result ->
                if (descriptor.parent == null && result.skippedTestCount > 0) {
                    throw new GradleException(
                            "webSecurityBoundaryTest forbids skipped tests: ${result.skippedTestCount}")
                }
            }
        }
    }
}

tasks.named('check') {
    dependsOn tasks.named('webSecurityBoundaryTest')
}
exit=0

