chore: record pre-existing uncommitted repository state

Snapshot of the in-flight state that already existed, identically, in both
this worktree and the main checkout before this session began: the initial
HTTP Client platform implementation (previously untracked), the redis-lab
removal, and the JPA / object-storage / notification integration work.

Kept separate from this session's HTTP Client review response, which lands
in the following commit, so the two bodies of work stay reviewable apart.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
DongHyeonka
2026-08-11 16:48:43 +09:00
co-authored by Claude Opus 5
parent 1a3b560678
commit 5f10b791d3
1857 changed files with 130925 additions and 72491 deletions
+78
View File
@@ -0,0 +1,78 @@
import java.util.regex.Pattern
import org.gradle.api.tasks.bundling.Jar
Closure<Boolean> isTraceableArchiveFor = { Jar archiveTask, String fileName ->
String baseName = Pattern.quote(archiveTask.archiveBaseName.get())
String classifier = archiveTask.archiveClassifier.orNull
String classifierPart = classifier == null || classifier.isBlank()
? ''
: "-${Pattern.quote(classifier)}"
fileName ==~ /^${baseName}-\d+\.\d+\.\d+\+[0-9a-f]{7,40}${classifierPart}\.jar$/
}
Closure<List<File>> staleTraceableArchivesFor = { Jar archiveTask ->
File outputDirectory = archiveTask.destinationDirectory.get().asFile
if (!outputDirectory.isDirectory()) {
return []
}
String currentName = archiveTask.archiveFileName.get()
List<File> stale = outputDirectory.listFiles({ File ignored, String fileName ->
isTraceableArchiveFor(archiveTask, fileName) && fileName != currentName
} as FilenameFilter)?.toList() ?: []
stale.sort { it.name }
}
tasks.register('cleanStaleTraceableJars') {
group = 'build'
description = 'Explicitly deletes older git-revision JARs from leaf build/libs directories.'
notCompatibleWithConfigurationCache(
'Inspects subproject Jar task models at execution time')
doLast {
int deleted = 0
subprojects.each { subproject ->
subproject.tasks.withType(Jar).each { Jar archiveTask ->
staleTraceableArchivesFor(archiveTask).each { File stale ->
if (!stale.delete()) {
throw new GradleException("cleanStaleTraceableJars: failed to delete ${stale}")
}
deleted++
logger.lifecycle("cleanStaleTraceableJars: deleted ${stale}")
}
}
}
logger.lifecycle("cleanStaleTraceableJars: deleted ${deleted} stale archive(s).")
}
}
tasks.register('verifyNoStaleTraceableJars') {
group = 'verification'
description = 'Fails without mutation when leaf build/libs directories retain old traceable JARs.'
notCompatibleWithConfigurationCache(
'Inspects subproject Jar task models at execution time')
doLast {
List<String> violations = []
subprojects.each { subproject ->
subproject.tasks.withType(Jar).each { Jar archiveTask ->
List<String> staleJars =
staleTraceableArchivesFor(archiveTask).collect { File stale -> stale.name }
if (!staleJars.isEmpty()) {
violations <<
"${archiveTask.path}: stale JAR(s) ${staleJars}; " +
"current archive is ${archiveTask.archiveFileName.get()}"
}
}
}
if (!violations.isEmpty()) {
throw new GradleException(
"verifyNoStaleTraceableJars: ${violations.size()} archive task(s) retain old " +
"traceable JARs. Run cleanStaleTraceableJars explicitly if removal is " +
"intended.\n ${violations.join('\n ')}")
}
logger.lifecycle(
'verifyNoStaleTraceableJars: OK — no stale traceable JARs in build/libs.')
}
}
+101
View File
@@ -0,0 +1,101 @@
import groovy.xml.XmlSlurper
Closure<Map<String, Object>> readJUnitEvidence = { String evidenceName, File resultDirectory ->
List<File> resultFiles = resultDirectory.isDirectory()
? rootProject.fileTree(resultDirectory) {
include 'TEST-*.xml'
}.files.toList().sort { left, right -> left.path <=> right.path }
: []
if (resultFiles.isEmpty()) {
throw new GradleException(
"${evidenceName}: no JUnit XML result files in ${resultDirectory}")
}
int totalTests = 0
int totalSkipped = 0
int totalFailures = 0
int totalErrors = 0
Set<String> executedClasses = new LinkedHashSet<>()
resultFiles.each { File resultFile ->
XmlSlurper parser = new XmlSlurper(false, false)
parser.setFeature('http://apache.org/xml/features/disallow-doctype-decl', true)
def suite
try {
suite = parser.parse(resultFile)
} catch (Exception exception) {
throw new GradleException(
"${evidenceName}: unreadable JUnit XML ${resultFile.name}", exception)
}
if (suite.name() != 'testsuite') {
throw new GradleException(
"${evidenceName}: ${resultFile.name} root must be testsuite")
}
Map<String, Integer> counts = [:]
['tests', 'skipped', 'failures', 'errors'].each { String attribute ->
String rawValue = suite.attributes()[attribute]?.toString()
if (!(rawValue ==~ /\d+/)) {
throw new GradleException(
"${evidenceName}: ${resultFile.name} has invalid ${attribute}='${rawValue}'")
}
counts[attribute] = rawValue.toInteger()
}
totalTests += counts.tests
totalSkipped += counts.skipped
totalFailures += counts.failures
totalErrors += counts.errors
suite.testcase.each { testCase ->
String className = testCase.attributes().classname?.toString()
if (className != null && !className.isBlank() && testCase.skipped.isEmpty()) {
executedClasses.add(className)
}
}
}
[
tests : totalTests,
skipped : totalSkipped,
failures : totalFailures,
errors : totalErrors,
executedClasses: executedClasses
]
}
Closure<Map<String, Object>> verifyNoSkipJUnitXml = {
String evidenceName, File resultDirectory ->
Map<String, Object> evidence = readJUnitEvidence(evidenceName, resultDirectory)
if (evidence.tests <= 0) {
throw new GradleException(
"${evidenceName}: requires a positive executed test count")
}
if (evidence.skipped > 0) {
throw new GradleException(
"${evidenceName}: forbids skipped tests: ${evidence.skipped}")
}
if (evidence.failures > 0 || evidence.errors > 0) {
throw new GradleException(
"${evidenceName}: failures=${evidence.failures}, errors=${evidence.errors}")
}
logger.lifecycle("${evidenceName}: ${evidence.tests} tests, ${evidence.skipped} skipped")
evidence
}
Closure<Map<String, Object>> verifyRequiredJUnitClasses = {
String evidenceName, File resultDirectory, List<String> requiredClasses ->
Map<String, Object> evidence = verifyNoSkipJUnitXml(evidenceName, resultDirectory)
Set<String> executedClasses = evidence.executedClasses as Set<String>
List<String> missingClasses = requiredClasses.findAll { String requiredClass ->
!executedClasses.any { String executedClass ->
executedClass == requiredClass || executedClass.startsWith(requiredClass + '$')
}
}
if (!missingClasses.isEmpty()) {
throw new GradleException(
"${evidenceName}: no executed test cases for required classes: ${missingClasses}")
}
evidence
}
rootProject.ext.readJUnitEvidence = readJUnitEvidence
rootProject.ext.verifyNoSkipJUnitXml = verifyNoSkipJUnitXml
rootProject.ext.verifyRequiredJUnitClasses = verifyRequiredJUnitClasses
+105
View File
@@ -0,0 +1,105 @@
Closure<String> renderPublicPathSnapshot = { File environmentFile ->
if (!environmentFile.isFile()) {
throw new GradleException(
"missing public-path environment file ${environmentFile}")
}
def valuePattern = ~/^SECURITY_PUBLIC_PATHS=(.*)$/
String raw = environmentFile.readLines('UTF-8').findResult { String line ->
def matcher = valuePattern.matcher(line)
matcher.matches() ? matcher.group(1) : null
} ?: ''
List<String> publicPaths = raw.split(',')
.collect { String value -> value.trim() }
.findAll { String value -> !value.isEmpty() }
.toSorted()
String header =
"# feature-security-operational-baseline D5 — deny-by-default public path snapshot.\n" +
"# SSOT: SECURITY_PUBLIC_PATHS (src/.env) -> SecurityConfig permitAll(); " +
"anyRequest authenticated.\n" +
"# Update only after review with: ./gradlew updatePublicPathSnapshot " +
"-PapprovePublicPathChange\n"
header + (publicPaths.isEmpty() ? '' : publicPaths.join('\n') + '\n')
}
File publicPathEnvironmentFile = rootProject.file('.env')
File publicPathSnapshotFile =
rootProject.file('../docs/security/public-paths-snapshot.txt')
boolean publicPathUpdateApproved = project.hasProperty('approvePublicPathChange')
def existingPublicPathEnvironment = providers.provider {
publicPathEnvironmentFile.isFile() ? publicPathEnvironmentFile : null
}
def existingPublicPathSnapshot = providers.provider {
publicPathSnapshotFile.isFile() ? publicPathSnapshotFile : null
}
tasks.register('verifyPublicPathSnapshot') {
group = 'verification'
description = 'Fails without mutation when the committed deny-by-default public path baseline drifts.'
inputs.file(existingPublicPathEnvironment).optional()
inputs.file(existingPublicPathSnapshot).optional()
inputs.property('updateApprovalRequested', publicPathUpdateApproved)
doLast {
if (publicPathUpdateApproved) {
throw new GradleException(
'verifyPublicPathSnapshot is read-only; use updatePublicPathSnapshot ' +
'-PapprovePublicPathChange for an intentional update.')
}
String canonical
try {
canonical = renderPublicPathSnapshot(publicPathEnvironmentFile)
} catch (GradleException exception) {
throw new GradleException(
"verifyPublicPathSnapshot: ${exception.message}", exception)
}
if (!publicPathSnapshotFile.isFile()) {
throw new GradleException(
"verifyPublicPathSnapshot: missing committed baseline ${publicPathSnapshotFile}")
}
String existing = publicPathSnapshotFile.getText('UTF-8')
if (existing != canonical) {
throw new GradleException(
"verifyPublicPathSnapshot: the deny-by-default public path surface changed.\n" +
" expected (snapshot):\n${existing}\n" +
" actual (SECURITY_PUBLIC_PATHS):\n${canonical}\n" +
'A protected endpoint may now be public. Review the change, then run:\n' +
' ./gradlew updatePublicPathSnapshot -PapprovePublicPathChange')
}
logger.lifecycle(
'verifyPublicPathSnapshot: OK — committed public paths are unchanged.')
}
}
tasks.register('updatePublicPathSnapshot') {
group = 'build setup'
description = 'Explicitly updates the committed public path baseline after security review.'
inputs.file(existingPublicPathEnvironment).optional()
inputs.property('approved', publicPathUpdateApproved)
outputs.file(publicPathSnapshotFile)
outputs.upToDateWhen { false }
doLast {
if (!publicPathUpdateApproved) {
throw new GradleException(
'updatePublicPathSnapshot requires -PapprovePublicPathChange')
}
String canonical
try {
canonical = renderPublicPathSnapshot(publicPathEnvironmentFile)
} catch (GradleException exception) {
throw new GradleException(
"updatePublicPathSnapshot: ${exception.message}", exception)
}
if (!publicPathSnapshotFile.parentFile.isDirectory()
&& !publicPathSnapshotFile.parentFile.mkdirs()) {
throw new GradleException(
"updatePublicPathSnapshot: failed to create ${publicPathSnapshotFile.parentFile}")
}
publicPathSnapshotFile.setText(canonical, 'UTF-8')
logger.lifecycle(
"updatePublicPathSnapshot: wrote reviewed baseline ${publicPathSnapshotFile}")
}
}
-5
View File
@@ -1,5 +0,0 @@
redis.minimum.image=redis:7.2.14-alpine@sha256:dfa18828cbc07b3ae6a95ec7343f6c214fdee2d836197b4be8e9904420762cd8
redis.below-minimum.image=redis:7.0.15-alpine@sha256:c9d92d840fd011c908f040592857c724ae6d877f2aba5c40ad963276507386b2
redis.next-minor.image=redis:7.4.9-alpine@sha256:6ab0b6e7381779332f97b8ca76193e45b0756f38d4c0dcda72dbb3c32061ab99
redis.approved.image=redis:7.4.9-alpine@sha256:6ab0b6e7381779332f97b8ca76193e45b0756f38d4c0dcda72dbb3c32061ab99
toxiproxy.image=ghcr.io/shopify/toxiproxy:2.12.0@sha256:9378ed52a28bc50edc1350f936f518f31fa95f0d15917d6eb40b8e376d1a214e
+150
View File
@@ -0,0 +1,150 @@
import groovy.json.JsonSlurper
import org.gradle.api.artifacts.ProjectDependency
def verifyRuntimeModuleMembership = tasks.register('verifyRuntimeModuleMembership') {
group = 'verification'
description = 'Verifies registry runtime membership against both shipped composition roots.'
File registryFile = rootProject.file('config/architecture/modules.json')
inputs.file(registryFile)
doLast {
if (!registryFile.isFile()) {
throw new GradleException("Missing module registry: ${registryFile}")
}
def registry = new JsonSlurper().parse(registryFile)
if (!(registry instanceof Map) || !(registry.modules instanceof List)) {
throw new GradleException('Module registry needs a modules list.')
}
if (!(registry.runtime_compositions instanceof List) || registry.runtime_compositions.isEmpty()) {
throw new GradleException('Module registry needs a non-empty runtime_compositions list.')
}
List<String> compositionIds = registry.runtime_compositions.withIndex().collect {
Object value, int index ->
if (!(value instanceof String) || (value as String).isBlank()) {
throw new GradleException(
"runtime_compositions entry ${index} must be a nonblank string.")
}
value as String
}
if (compositionIds.toSet().size() != compositionIds.size()) {
throw new GradleException('runtime_compositions must not contain duplicates.')
}
Map<String, Object> modulesById = [:]
Map<String, String> moduleIdByGradlePath = [:]
registry.modules.eachWithIndex { Object rawModule, int index ->
if (!(rawModule instanceof Map)) {
throw new GradleException("Module registry entry ${index} must be an object.")
}
Map<String, Object> module = rawModule as Map<String, Object>
String moduleId = module.id as String
if (moduleId == null || moduleId.isBlank()) {
throw new GradleException("Module registry entry ${index} needs a nonblank id.")
}
if (!(module.runtime_memberships instanceof List)) {
throw new GradleException(
"Module registry entry '${moduleId}' needs a runtime_memberships list.")
}
List<String> memberships = module.runtime_memberships.withIndex().collect {
Object membership, int membershipIndex ->
if (!(membership instanceof String) || (membership as String).isBlank()) {
throw new GradleException(
"Module registry entry '${moduleId}' has a blank/non-string " +
"runtime membership at index ${membershipIndex}.")
}
membership as String
}
if (memberships.toSet().size() != memberships.size()) {
throw new GradleException(
"Module registry entry '${moduleId}' has duplicate runtime memberships.")
}
Set<String> unknownMemberships = memberships.toSet() - compositionIds.toSet()
if (!unknownMemberships.isEmpty()) {
throw new GradleException(
"Module registry entry '${moduleId}' has unknown runtime membership(s) " +
"${unknownMemberships.toSorted()}.")
}
if (modulesById.put(moduleId, module) != null) {
throw new GradleException("Module registry contains duplicate id '${moduleId}'.")
}
String gradlePath = module.gradle_path as String
if (gradlePath == null || gradlePath.isBlank()) {
throw new GradleException(
"Module registry entry '${moduleId}' needs a nonblank gradle_path.")
}
if (moduleIdByGradlePath.put(gradlePath, moduleId) != null) {
throw new GradleException(
"Module registry contains duplicate Gradle path '${gradlePath}'.")
}
}
compositionIds.each { String compositionId ->
Map<String, Object> composition = modulesById[compositionId] as Map<String, Object>
if (composition == null) {
throw new GradleException(
"Runtime composition '${compositionId}' is not a registered module id.")
}
List<String> ownMemberships = composition.runtime_memberships as List<String>
if (!ownMemberships.contains(compositionId)) {
throw new GradleException(
"Runtime composition '${compositionId}' must include itself in runtime_memberships.")
}
String compositionGradlePath = composition.gradle_path as String
Project compositionProject = rootProject.findProject(compositionGradlePath)
if (compositionProject == null) {
throw new GradleException(
"Runtime composition '${compositionId}' references missing Gradle project " +
"'${compositionGradlePath}'.")
}
Set<String> expected = registry.modules.findAll { Object rawModule ->
Map<String, Object> module = rawModule as Map<String, Object>
(module.runtime_memberships as List).contains(compositionId) &&
module.id != compositionId
}.collect { Object rawModule ->
(rawModule as Map<String, Object>).id as String
}.toSet()
Set<String> actual = ['api', 'implementation', 'compileOnly', 'runtimeOnly']
.collect { String configurationName ->
compositionProject.configurations.findByName(configurationName)
}
.findAll { it != null }
.collectMany { configuration ->
configuration.dependencies.withType(ProjectDependency).collect {
ProjectDependency dependency ->
String dependencyId = moduleIdByGradlePath[dependency.path]
if (dependencyId == null) {
throw new GradleException(
"Runtime composition '${compositionId}' depends on unregistered " +
"Gradle project '${dependency.path}'.")
}
dependencyId
}
}
.toSet()
Set<String> unregistered = actual - expected
Set<String> missing = expected - actual
if (!unregistered.isEmpty() || !missing.isEmpty()) {
List<String> violations = []
if (!unregistered.isEmpty()) {
violations << "unregistered runtime dependencies ${unregistered.toSorted()}"
}
if (!missing.isEmpty()) {
violations << "missing registered runtime dependencies ${missing.toSorted()}"
}
throw new GradleException(
"Runtime composition '${compositionId}' membership drift: " +
violations.join('; ') + '.')
}
}
logger.lifecycle(
"verifyRuntimeModuleMembership: ${compositionIds.size()} runtime composition(s) " +
'match the registry')
}
}
rootProject.ext.verifyRuntimeModuleMembership = verifyRuntimeModuleMembership
+113
View File
@@ -0,0 +1,113 @@
// Owner-local convention for release/qualification lanes that must never pass without executing
// every explicitly required JUnit class. Apply junit-evidence.gradle before this script.
ext.registerStrictQualificationTest = { Map<String, ?> specification ->
String taskName = specification.name as String
def qualificationSourceSet = specification.sourceSet
List<String> requiredClasses = (specification.requiredClasses ?: []) as List<String>
if (taskName == null || taskName.isBlank()) {
throw new GradleException('A strict qualification task name is required.')
}
if (qualificationSourceSet == null) {
throw new GradleException("${taskName} requires an owner source set.")
}
if (!sourceSets.findByName(qualificationSourceSet.name).is(qualificationSourceSet)) {
throw new GradleException(
"${taskName} source set '${qualificationSourceSet.name}' does not belong to owner project ${project.path}.")
}
if (requiredClasses.isEmpty() || requiredClasses.any { it == null || it.isBlank() }) {
throw new GradleException("${taskName} must name at least one required test FQCN.")
}
if (requiredClasses.toSet().size() != requiredClasses.size()) {
throw new GradleException("${taskName} contains duplicate required test FQCNs.")
}
def junitXmlOutput = specification.junitXmlOutput ?:
layout.buildDirectory.dir("test-results/${taskName}")
def binaryResultsOutput = specification.binaryResultsOutput ?:
layout.buildDirectory.dir("test-results/${taskName}/binary")
def requiredClassesCheck = tasks.register("${taskName}RequiredClasses") {
group = 'verification'
description = "Fails when ${taskName} did not compile every required test class."
dependsOn qualificationSourceSet.classesTaskName
inputs.files(qualificationSourceSet.output.classesDirs)
outputs.upToDateWhen { false }
doLast {
Set<File> classDirectories = qualificationSourceSet.output.classesDirs.files
boolean hasAnyClass = classDirectories.any { File directory ->
directory.isDirectory() &&
!fileTree(directory).matching { include '**/*.class' }.isEmpty()
}
if (!hasAnyClass) {
throw new GradleException(
"${taskName} source set produced no test class files.")
}
List<String> missingClasses = requiredClasses.findAll { String requiredClass ->
String relativeClassFile = requiredClass.replace('.', '/') + '.class'
!classDirectories.any { File directory ->
new File(directory, relativeClassFile).isFile()
}
}
if (!missingClasses.isEmpty()) {
throw new GradleException(
"${taskName} is missing required test class files: ${missingClasses}")
}
File staleEvidence = junitXmlOutput.get().asFile
if (staleEvidence.exists() && !project.delete(staleEvidence)) {
throw new GradleException(
"${taskName} could not delete stale JUnit XML: ${staleEvidence}")
}
}
}
def qualificationTest = tasks.register(taskName, Test) {
group = 'verification'
description = specification.description ?:
"Runs exact no-skip qualification evidence for ${project.path}."
dependsOn requiredClassesCheck
testClassesDirs = qualificationSourceSet.output.classesDirs
classpath = qualificationSourceSet.runtimeClasspath
useJUnitPlatform()
filter {
requiredClasses.each { String requiredClass ->
includeTestsMatching(requiredClass)
}
failOnNoMatchingTests = true
}
failOnNoDiscoveredTests = true
reports.junitXml.required = true
reports.junitXml.outputLocation = junitXmlOutput
reports.html.required = false
binaryResultsDirectory = binaryResultsOutput
outputs.upToDateWhen { false }
jvmArgs '-Duser.timezone=UTC'
afterSuite { descriptor, result ->
if (descriptor.parent == null && result.skippedTestCount > 0) {
throw new GradleException(
"${taskName} forbids skipped tests: ${result.skippedTestCount}")
}
}
}
def evidenceCheck = tasks.register("${taskName}Evidence") {
group = 'verification'
description = "Fails unless ${taskName} executed every required test class without skips."
mustRunAfter qualificationTest
outputs.upToDateWhen { false }
doLast {
if (!rootProject.ext.has('verifyRequiredJUnitClasses')) {
throw new GradleException(
"${taskName} requires gradle/junit-evidence.gradle.")
}
rootProject.ext.verifyRequiredJUnitClasses(
taskName, junitXmlOutput.get().asFile, requiredClasses)
}
}
qualificationTest.configure {
finalizedBy evidenceCheck
}
qualificationTest
}
+56
View File
@@ -0,0 +1,56 @@
import org.gradle.api.GradleException
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Classpath
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.testing.Test
import org.gradle.process.CommandLineArgumentProvider
abstract class MockitoAgentArgumentProvider implements CommandLineArgumentProvider {
@Classpath
abstract ConfigurableFileCollection getMockitoCoreClasspath()
@Input
abstract Property<String> getOwner()
@Override
Iterable<String> asArguments() {
List<File> candidates = mockitoCoreClasspath.files.findAll { File file ->
file.isFile() && file.name ==~ 'mockito-core-[^/]+\\.jar'
}.sort { File left, File right -> left.absolutePath <=> right.absolutePath }
if (candidates.size() != 1) {
throw new GradleException(
"${owner.get()}: expected exactly one mockito-core JAR for the test JVM, " +
"found ${candidates.size()}: " +
candidates.collect { it.absolutePath })
}
File mockitoCore = candidates[0]
["-javaagent:${mockitoCore.absolutePath}", '-Xshare:off']
}
}
rootProject.subprojects { Project target ->
target.pluginManager.withPlugin('java') {
target.pluginManager.withPlugin('io.spring.dependency-management') {
def mockitoAgentDependencies =
target.configurations.dependencyScope('mockitoAgentDependencies')
def mockitoAgent = target.configurations.resolvable('mockitoAgent') {
description = 'Mockito core JAR used only as a Test JVM startup agent.'
extendsFrom(mockitoAgentDependencies.get())
transitive = false
}
target.dependencies.add(
mockitoAgentDependencies.get().name, 'org.mockito:mockito-core')
target.tasks.withType(Test).configureEach {
def provider = target.objects.newInstance(MockitoAgentArgumentProvider)
provider.mockitoCoreClasspath.from(mockitoAgent)
provider.owner.set("${target.path}:${name}")
jvmArgumentProviders.add(provider)
}
}
}
}
Binary file not shown.
+1
View File
@@ -1,6 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-bin.zip
distributionSha256Sum=8fad3d78296ca518113f3d29016617c7f9367dc005f932bd9d93bf45ba46072b
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME