Sub-scope 01 (fileserver) governance / build / config / activation - manifest and probes revision=a24ece9cf797f7ea647e33bf846b115208ed1ba5 generatedAt=2026-08-30T01:07:08+00:00 === OWNED FILES === 0526605004cba24b4653ce9dda3e344447de5ad1 CLAUDE.md 66 8c05831905836fcfb3db444e820c1d7e2f43aafd README.md 140 a34958ba1fc2596886d2d2cb5ad648b038c57738 build.gradle 16 17910be715e0ad53d5f8b70ef68c36f701dcf583 gradle.lockfile 150 18d2700b9b403e46ace4d4bf49724592d4e6a90e src/main/java/dev/caskeleton/adapter/outbound/fileserver/FileExportConfig.java 73 dc9a6cd8f5dd74eff3b7982fb88f5ded240271da src/main/java/dev/caskeleton/adapter/outbound/fileserver/FileExportSettings.java 92 c8413a3ed8501115322274f62a1bdb5e33e79776 src/main/java/dev/caskeleton/adapter/outbound/fileserver/FileserverR2Config.java 92 5ed171f83c1f24567d1e6a588a9a6dea6926983d src/main/java/dev/caskeleton/adapter/outbound/fileserver/FileserverR2Settings.java 41 42d188e20225fc93385e7b5cfa4973eeaf0b0379 src/main/java/dev/caskeleton/adapter/outbound/fileserver/FileserverR2Validation.java 189 d8ebf9ffb1c19ef77525933817127f4a4a9ec689 src/main/java/dev/caskeleton/adapter/outbound/fileserver/FileserverActivationValidator.java 22 561a808299750095da0080492ac904d9a69c2cb9 src/main/java/dev/caskeleton/adapter/outbound/fileserver/platform/local/LocalStorageProperties.java 45 1769f2fd207ec9e9bf37841413d33492319faa21 src/test/java/dev/caskeleton/adapter/outbound/fileserver/FilePublicationConfigTest.java 108 b32c5f7b7a77b8510d1e13922e2a7c7d454027fb src/test/java/dev/caskeleton/adapter/outbound/fileserver/FileserverR2ConfigTest.java 530 === 8.1 reachability: how do these @Configuration classes get loaded? === $ git ls-files 'adapter/outbound/fileserver' | grep -i 'META-INF\|imports\|spring.factories' exit=1 $ git grep -rn 'FileExportConfig\|FileserverR2Config' -- . | grep -v "^adapter/outbound/fileserver/" | head -20 app-bootstrap/src/test/java/dev/caskeleton/adapter/outbound/OptionalAdapterBeanGatingTest.java:7:import dev.caskeleton.adapter.outbound.fileserver.FileExportConfig; app-bootstrap/src/test/java/dev/caskeleton/adapter/outbound/OptionalAdapterBeanGatingTest.java:8:import dev.caskeleton.adapter.outbound.fileserver.FileserverR2Config; app-bootstrap/src/test/java/dev/caskeleton/adapter/outbound/OptionalAdapterBeanGatingTest.java:72: FileExportConfig.class, app-bootstrap/src/test/java/dev/caskeleton/adapter/outbound/OptionalAdapterBeanGatingTest.java:73: FileserverR2Config.class, exit=0 $ git grep -rn 'app.fileserver.enabled\|app.file-export.enabled' -- app-bootstrap/src/main sample-portfolio/src/main 2>/dev/null | head -20 exit=0 === 8.1b who else validates the R1/R2 ambiguity === $ git grep -rn 'rejectAmbiguous\|FileserverActivationValidator' -- . | grep -v '^Binary' adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/FileExportConfig.java:33: FileserverActivationValidator.rejectAmbiguous(environment); adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/FileExportConfig.java:49: FileserverActivationValidator.rejectAmbiguous(environment); adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/FileserverActivationValidator.java:8:final class FileserverActivationValidator { adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/FileserverActivationValidator.java:10: private FileserverActivationValidator() {} adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/FileserverActivationValidator.java:12: static void rejectAmbiguous(Environment environment) { adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/FileserverR2Config.java:31: FileserverActivationValidator.rejectAmbiguous(environment); exit=0 $ git grep -n 'file-export\|fileserver' app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverStartupValidator.java | head -20 app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverStartupValidator.java:1:package dev.caskeleton.bootstrap.autoconfigure.fileserver; app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverStartupValidator.java:3:import dev.caskeleton.adapter.outbound.fileserver.platform.local.LocalStorageProbeResult; app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverStartupValidator.java:4:import dev.caskeleton.application.fileserver.api.content.PublishMode; app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverStartupValidator.java:102: "fileserver startup validation failed: " + String.join("; ", violations)); exit=0 === 8.2 conditional siblings: R1 vs R2 settings handling === $ grep -n 'ConfigurationProperties' adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/FileExportSettings.java adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/FileserverR2Settings.java adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/FileExportSettings.java:3:import org.springframework.boot.context.properties.ConfigurationProperties; adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/FileExportSettings.java:10:@ConfigurationProperties(prefix = "app.file-export") adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/FileserverR2Settings.java:4:import org.springframework.boot.context.properties.ConfigurationProperties; adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/FileserverR2Settings.java:7:@ConfigurationProperties(prefix = "app.fileserver", ignoreUnknownFields = false) exit=0 $ grep -n 'configuredRoot\|toAbsolutePath\|createDirectories' -A 3 adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/FileExportConfig.java 34: Path publicationRoot = configuredRoot(properties.getBaseDirectory(), "base-directory"); 35: Path legacyRoot = configuredRoot(properties.getLegacyBaseDirectory(), "legacy-base-directory"); 36- Path canonicalPublicationRoot = canonicalDirectory(publicationRoot); 37- Path canonicalLegacyRoot = canonicalDirectory(legacyRoot); 38- if (canonicalPublicationRoot.startsWith(canonicalLegacyRoot) -- 53: configuredRoot(properties.getBaseDirectory(), "base-directory"), 54- properties.getMaximumRows(), 55- properties.getMaximumEncodedBytes())); 56- } -- 58: private static Path configuredRoot(String value, String property) { 59- if (value == null || value.isBlank()) { 60- throw new IllegalArgumentException("app.file-export." + property + " must be non-blank"); 61- } 62: return Path.of(value).toAbsolutePath().normalize(); 63- } 64- 65- private static Path canonicalDirectory(Path root) { -- 67: Files.createDirectories(root); 68- return root.toRealPath(); 69- } catch (IOException exception) { 70- throw new IllegalStateException("fileserver root cannot be canonicalized", exception); exit=0 $ grep -n 'requireAbsoluteNormalizedPath' -A 8 adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/FileserverR2Validation.java | head -24 51: static Path requireAbsoluteNormalizedPath(String label, String configuredPath) { 52- String value = requireNonBlank(label, configuredPath); 53- try { 54: return requireAbsoluteNormalizedPath(label, Path.of(value)); 55- } catch (InvalidPathException exception) { 56- throw new IllegalArgumentException(label + " must be a valid path", exception); 57- } 58- } 59- 60: static Path requireAbsoluteNormalizedPath(String label, Path path) { 61- Objects.requireNonNull(path, label + " must be non-null"); 62- if (!path.isAbsolute()) { 63- throw new IllegalArgumentException(label + " must be absolute"); 64- } 65- Path normalized = path.normalize(); 66- if (!path.equals(normalized)) { 67- throw new IllegalArgumentException(label + " must already be normalized"); 68- } exit=0 $ grep -n 'unknownConfigurationFieldIsRejected' -A 14 adapter/outbound/fileserver/src/test/java/dev/caskeleton/adapter/outbound/fileserver/FileserverR2ConfigTest.java 312: void unknownConfigurationFieldIsRejectedInsteadOfSilentlyIgnored() throws IOException { 313- RootFixture root = preProvisionedRoot("unknown-field"); 314- 315- r2Runner 316- .withPropertyValues(validProperties(root, "local-export")) 317- .withPropertyValues(providerProperty("local-primary", "strict-path-securty", "true")) 318- .run( 319- context -> { 320- assertThat(context).hasFailed(); 321- assertThat(root.path().resolve(".ca-fileserver")).doesNotExist(); 322- assertThat(root.path().resolve("data")).doesNotExist(); 323- }); 324- } 325- 326- @ParameterizedTest exit=0 $ git grep -rn 'app.file-export' -- 'adapter/outbound/fileserver/src/test/java/dev/caskeleton/adapter/outbound/fileserver' | head adapter/outbound/fileserver/src/test/java/dev/caskeleton/adapter/outbound/fileserver/FilePublicationConfigTest.java:33: "app.file-export.enabled=true", adapter/outbound/fileserver/src/test/java/dev/caskeleton/adapter/outbound/fileserver/FilePublicationConfigTest.java:34: "app.file-export.base-directory=build/test-files", adapter/outbound/fileserver/src/test/java/dev/caskeleton/adapter/outbound/fileserver/FilePublicationConfigTest.java:35: "app.file-export.destination-id=nightly-export", adapter/outbound/fileserver/src/test/java/dev/caskeleton/adapter/outbound/fileserver/FilePublicationConfigTest.java:36: "app.file-export.maximum-rows=125", adapter/outbound/fileserver/src/test/java/dev/caskeleton/adapter/outbound/fileserver/FilePublicationConfigTest.java:37: "app.file-export.maximum-encoded-bytes=4096") adapter/outbound/fileserver/src/test/java/dev/caskeleton/adapter/outbound/fileserver/FilePublicationConfigTest.java:54: "app.file-export.enabled=true", adapter/outbound/fileserver/src/test/java/dev/caskeleton/adapter/outbound/fileserver/FilePublicationConfigTest.java:55: "app.file-export.base-directory=build/test-files", adapter/outbound/fileserver/src/test/java/dev/caskeleton/adapter/outbound/fileserver/FilePublicationConfigTest.java:56: "app.file-export.legacy-enabled=true", adapter/outbound/fileserver/src/test/java/dev/caskeleton/adapter/outbound/fileserver/FilePublicationConfigTest.java:57: "app.file-export.legacy-base-directory=build/test-files-legacy") adapter/outbound/fileserver/src/test/java/dev/caskeleton/adapter/outbound/fileserver/FilePublicationConfigTest.java:70: .withPropertyValues("app.file-export.enabled=true", "app.file-export.base-directory= ") exit=0 === 8.2b does any test cover an unknown app.file-export.* key? === $ grep -c 'ignoreUnknownFields' adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/FileExportSettings.java adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/FileserverR2Settings.java adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/FileExportSettings.java:0 adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/FileserverR2Settings.java:1 exit=0 === 8.3 the three rejectAmbiguous call sites and what each protects === $ grep -n 'rejectAmbiguous' -B 6 adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/FileExportConfig.java adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/FileserverR2Config.java adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/FileExportConfig.java-27- @ConditionalOnProperty( adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/FileExportConfig.java-28- prefix = "app.file-export", adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/FileExportConfig.java-29- name = {"enabled", "legacy-enabled"}, adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/FileExportConfig.java-30- havingValue = "true") adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/FileExportConfig.java-31- public FileExportPort filesystemCsvExportPort( adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/FileExportConfig.java-32- FileExportSettings properties, Environment environment) { adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/FileExportConfig.java:33: FileserverActivationValidator.rejectAmbiguous(environment); -- adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/FileExportConfig.java-43- } adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/FileExportConfig.java-44- adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/FileExportConfig.java-45- @Bean adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/FileExportConfig.java-46- @ConditionalOnProperty(prefix = "app.file-export", name = "enabled", havingValue = "true") adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/FileExportConfig.java-47- public FilePublicationPort localFilePublicationPort( adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/FileExportConfig.java-48- FileExportSettings properties, Environment environment) { adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/FileExportConfig.java:49: FileserverActivationValidator.rejectAmbiguous(environment); -- adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/FileserverR2Config.java-25- private static final HexFormat HEX = HexFormat.of(); adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/FileserverR2Config.java-26- adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/FileserverR2Config.java-27- @Bean adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/FileserverR2Config.java-28- @ConditionalOnProperty(prefix = "app.fileserver", name = "enabled", havingValue = "true") adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/FileserverR2Config.java-29- public FilePublicationPort routingFilePublicationPort( adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/FileserverR2Config.java-30- FileserverR2Settings settings, Environment environment) { adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/FileserverR2Config.java:31: FileserverActivationValidator.rejectAmbiguous(environment); exit=0 === 8.4 documentation drift === $ grep -n 'FilePublicationContractTest' adapter/outbound/fileserver/README.md 117:- `FilePublicationContractTest`: framework-free values and invalid contract inputs. exit=0 $ git grep -rln 'FilePublicationContractTest' -- . | head adapter/outbound/fileserver/README.md application-core/src/test/java/dev/caskeleton/application/filepublication/FilePublicationContractTest.java exit=0 $ grep -rn 'app.fileserver' app-bootstrap/src/main/resources/application.yml | head -20 193: # Same placeholders as app.fileserver-platform.upload.*, deliberately. Two independent 833: # HTTP Fileserver platform — a different capability from app.fileserver above, which publishes exit=0 $ grep -rn 'file-export' app-bootstrap/src/main/resources/application.yml | head -10 exit=0 --- forward reference for sub-scopes 5: what CLAUDE.md says is NOT implemented $ grep -n 'not implemented\|Advertising' adapter/outbound/fileserver/CLAUDE.md adapter/outbound/fileserver/README.md adapter/outbound/fileserver/CLAUDE.md:48:- Advertising `FILE_AND_DIRECTORY_SYNC` as physical device/controller/replica/site power-loss adapter/outbound/fileserver/CLAUDE.md:50:- Advertising `shared-mounted`/NFS, SFTP, cross-node fencing, reaper, retention, quota, adapter/outbound/fileserver/README.md:103:`shared-mounted`/NFS and SFTP providers are not implemented. Cross-node producer fencing, adapter/outbound/fileserver/README.md:105:tracing, and audit are also not implemented. No setting or bean for those capabilities is exposed. exit=0 $ git ls-files 'adapter/outbound/fileserver/src/main' | grep -iE 'audit|health|usage|orphan|reconcil' adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/platform/audit/StructuredAdminAuditAdapter.java adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/platform/audit/StructuredFileserverAuditAdapter.java adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/platform/local/LocalOrphanScanAdapter.java adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/platform/local/LocalReconciliationContentProbe.java adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/platform/local/LocalStorageHealthAdapter.java adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/platform/local/LocalStorageUsageProbe.java exit=0 === 8.1c how production actually loads the two @Configuration classes === $ grep -n 'SpringBootApplication\|ComponentScan\|Import\|scanBasePackages' app-bootstrap/src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java 9:import org.springframework.context.annotation.ComponentScan; 16: * {@code @ComponentScan} rather than {@code @SpringBootApplication}, because the scan needs an 18: * {@code @SpringBootApplication} contributes and must stay. 41:@ComponentScan( 50: @ComponentScan.Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), 51: @ComponentScan.Filter( 54: @ComponentScan.Filter( exit=0 $ grep -n 'fileserver' app-bootstrap/src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java 61: "dev.caskeleton.bootstrap.autoconfigure.fileserver", 75: "dev.caskeleton.adapter.outbound.fileserver", 103: + "|dev\\.caskeleton\\.adapter\\.inbound\\.web\\.fileserver\\.admin\\..*" exit=0 === 8.4e where the two selectors are actually defaulted === $ grep -rn 'app\.fileserver\.\|app\.file-export\.' app-bootstrap/src/main/resources/ sample-portfolio/src/main/resources/ 2>/dev/null | head exit=0 $ sed -n '190,196p;830,840p' app-bootstrap/src/main/resources/application.yml # GlobalExceptionHandler#handleMaxUploadSizeExceededException), never a raw 500. # Multipart-specific upload limits (UPLOAD_SIZE_EXCEEDED) are refined by # feature-file-resource-handling-contract. # Same placeholders as app.fileserver-platform.upload.*, deliberately. Two independent # ceilings meant the servlet container rejected at 10MB whatever the Fileserver policy said, # so a 100MB upload failed before any Fileserver code — including its error mapping — ran, # and the configured limit described a policy nobody could reach. mount-sentinel-sha256: ${APP_FILESERVER_LOCAL_MOUNT_SENTINEL_SHA256:} expected-owner: ${APP_FILESERVER_LOCAL_EXPECTED_OWNER:} maximum-root-mode: "0750" # HTTP Fileserver platform — a different capability from app.fileserver above, which publishes # tabular exports. This one serves upload/download/lifecycle routes over HTTP and owns its own # namespace so the two cannot be switched on by accident together. # # While enabled=false none of the detail below is bound: FileserverPlatformAutoConfiguration # binds the block itself, and it is not processed until the master switch is true. Unknown keys # under this prefix are refused rather than ignored. # Outbound HTTP Client platform. Only the master switch lives here. exit=0 === 8.4f is the platform/** tree exposed as beans by app-bootstrap? (lead for sub-scope 05) === $ git grep -rn 'StructuredFileserverAuditAdapter\|StructuredAdminAuditAdapter\|LocalStorageHealthAdapter\|LocalStorageUsageProbe\|LocalOrphanScanAdapter\|LocalReconciliationContentProbe' -- app-bootstrap/src/main | head -20 app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverSecurityConfiguration.java:3:import dev.caskeleton.adapter.outbound.fileserver.platform.audit.StructuredAdminAuditAdapter; app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverSecurityConfiguration.java:4:import dev.caskeleton.adapter.outbound.fileserver.platform.audit.StructuredFileserverAuditAdapter; app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverSecurityConfiguration.java:71: return new StructuredAdminAuditAdapter(); app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverSecurityConfiguration.java:77: return new StructuredFileserverAuditAdapter(); app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverStorageConfiguration.java:7:import dev.caskeleton.adapter.outbound.fileserver.platform.local.LocalOrphanScanAdapter; app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverStorageConfiguration.java:8:import dev.caskeleton.adapter.outbound.fileserver.platform.local.LocalReconciliationContentProbe; app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverStorageConfiguration.java:10:import dev.caskeleton.adapter.outbound.fileserver.platform.local.LocalStorageHealthAdapter; app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverStorageConfiguration.java:13:import dev.caskeleton.adapter.outbound.fileserver.platform.local.LocalStorageUsageProbe; app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverStorageConfiguration.java:179: return new LocalStorageHealthAdapter(properties, probeResult, store); app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverStorageConfiguration.java:189: return new LocalOrphanScanAdapter( app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverStorageConfiguration.java:196: return new LocalStorageUsageProbe(properties.root()); app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverStorageConfiguration.java:203: return new LocalReconciliationContentProbe(store, stagingLocator); exit=0