# 등록 순서 — 이 순서가 결과를 정한다
        .authorizeHttpRequests(
            auth -> {
              if (publicPaths.length > 0) {
                auth.requestMatchers(publicPaths).permitAll();
              }
              // Ordered before the authenticated catch-all: a management path must be refused at
              // the transport, not by an application policy the request has already passed.
              for (RestrictedPathRule rule : restricted) {
                auth.requestMatchers(rule.pathPattern()).hasAnyAuthority(rule.authorities());
              }
              auth.anyRequest().authenticated();
            })

# 규칙 자신은 약해지는 것을 막는다
30:    if (requiredAuthorities.isEmpty()) {
31-      throw new IllegalArgumentException(
32-          "requiredAuthorities must not be empty: a rule that requires nothing is weaker than the "
33-              + "authenticated default it replaces");
34-    }

# 규칙을 내놓는 자리
main · FileserverAdminPlaneConfiguration.java:36:    return new RestrictedPathRule(ADMIN_PATH_PATTERN, settings.security().adminRoles());

# 프로덕션이 등록하는 패턴과 그것을 켜야 하는 두 스위치
@ConditionalOnProperty(
    prefix = "app.fileserver-platform.admin",
    name = "enabled",
    havingValue = "true")
  static final String ADMIN_PATH_PATTERN = "/internal/fileserver/**";
  -- 그 설정 클래스를 이름으로 부르는 자리 전부
src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverAdminManagementContextConfiguration.java:20: * role gate contributed by {@link FileserverAdminPlaneConfiguration} stays: the two guards cover
src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverAdminPlaneConfiguration.java:29:public class FileserverAdminPlaneConfiguration {
src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverPlatformAutoConfiguration.java:71:  FileserverAdminPlaneConfiguration.class,
  -- 그 유일한 @Import 가 든 자동설정과 그 조건
@AutoConfiguration
@ConditionalOnProperty(
    prefix = FileserverPlatformSettings.PREFIX,
    name = "enabled",
    havingValue = "true")
@Import({
  FileserverStorageConfiguration.class,
  FileserverSecurityConfiguration.class,
  FileserverServiceConfiguration.class,
  FileserverCleanupConfiguration.class,
  FileserverAdminPlaneConfiguration.class,
  FileserverStartupConfiguration.class
})
49:  public static final String PREFIX = "app.fileserver-platform";
  -- 스캔이 그 패키지를 제외하므로 다른 길이 없다
    excludeFilters = {
      @ComponentScan.Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
      @ComponentScan.Filter(
          type = FilterType.CUSTOM,
          classes = AutoConfigurationExcludeFilter.class),
      @ComponentScan.Filter(
          type = FilterType.REGEX,
          pattern = CaSkeletonApplication.AUTO_CONFIGURED_PACKAGES)
    })
  static final String AUTO_CONFIGURED_PACKAGES =
      "dev\\.caskeleton\\.bootstrap\\.autoconfigure\\..*"
          + "|dev\\.caskeleton\\.bootstrap\\.notification\\..*"
          + "|dev\\.caskeleton\\.adapter\\.inbound\\.web\\.fileserver\\.admin\\..*"
          + "|dev\\.caskeleton\\.adapter\\.outbound\\.mongo\\..*"
          + "|dev\\.caskeleton\\.adapter\\.outbound\\.messaging\\..*"
          + "|dev\\.caskeleton\\.adapter\\.outbound\\.notification\\..*"
          + "|dev\\.caskeleton\\.adapter\\.outbound\\.persistence\\..*"
          + "|dev\\.caskeleton\\.adapter\\.inbound\\.graphql\\..*"
          + "|dev\\.caskeleton\\.adapter\\.inbound\\.web\\.mvc\\.error\\..*"
          + "|dev\\.caskeleton\\.adapter\\.inbound\\.web\\.mvc\\.budget\\..*"
          + "|dev\\.caskeleton\\.adapter\\.inbound\\.web\\.mvc\\.operation\\..*"
          + "|dev\\.caskeleton\\.adapter\\.inbound\\.web\\.webflux\\.error\\..*"
          + "|dev\\.caskeleton\\.adapter\\.inbound\\.web\\.webflux\\.operation\\..*"
          + "|dev\\.caskeleton\\.messaging\\..*";
  -- 관리 라우트 자체는 관리 컨텍스트에 등록되고 포트는 따로 출하된다
@ManagementContextConfiguration(value = ManagementContextType.ANY, proxyBeanMethods = false)
@ConditionalOnProperty(
    prefix = "app.fileserver-platform",
    name = {"enabled", "admin.enabled"},
    havingValue = "true")
@Import(FileserverAdminController.class)
249:    port: ${MANAGEMENT_SERVER_PORT:9001}
317:  port: ${APP_SERVER_PORT:8080}
694:    public-paths: ${SECURITY_PUBLIC_PATHS:${PRESENTATION_API_BASE_PATH:/v1}/healthcheck}
854:    enabled: ${APP_FILESERVER_PLATFORM_ENABLED:false}
899:      admin-roles: ${APP_FILESERVER_PLATFORM_SECURITY_ADMIN_ROLES:ROLE_FILE_ADMIN}
911:      enabled: ${APP_FILESERVER_PLATFORM_ADMIN_ENABLED:false}

# 공개 경로를 읽는 자리 — 접근자로 읽는 쪽과 원시 키로 읽는 쪽
test · SecuritySettingsTest.java:54:              assertThat(context.getBean(SecuritySettings.class).publicPaths())
test · SecuritySettingsTest.java:67:    assertThat(settings.publicPaths()).containsExactly("/healthcheck");
test · SecuritySettingsTest.java:68:    assertThat(settings.publicPaths()).isUnmodifiable();
main · SecurityConfig.java:75:    String[] publicPaths = securitySettings.publicPaths().toArray(new String[0]);
  -- 저장소 전체에서 그 키를 쥔 파일. 점 표기와 환경 변수와 중첩 YAML 세 형태로 찾는다
  ~  .superpowers/sdd/2026-08-02-warning-zero-build-refactoring/task-8-gradle-review.diff
     docs/security/public-paths-snapshot.txt
     docs/superpowers/plans/2026-07-25-ci-control-plane-recovery.md
     docs/superpowers/plans/2026-07-28-notification-production-capability.md
  ~  src/.env
     src/README.md
     src/adapter/inbound/web/src/test/java/dev/caskeleton/adapter/inbound/web/auth/CorsSecurityFilterIntegrationTest.java
     src/adapter/inbound/web/src/test/java/dev/caskeleton/adapter/inbound/web/auth/JwtJwksSecurityFilterIntegrationTest.java
     src/adapter/inbound/web/src/test/java/dev/caskeleton/adapter/inbound/web/auth/SecurityModeWebContractTest.java
     src/adapter/inbound/web/src/test/java/dev/caskeleton/adapter/inbound/web/settings/SecuritySettingsTest.java
     src/app-bootstrap/src/functionalTest/java/dev/caskeleton/bootstrap/contract/BuildVerificationPurityContractTest.java
     src/app-bootstrap/src/main/resources/application-local.yml
     src/app-bootstrap/src/main/resources/application.yml
     src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/contract/ProfileSeparationContractTest.java
     src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/security/PublicHealthPathAgreementTest.java
     src/app-bootstrap/src/test/resources/application-test.yml
     src/gradle/public-path-snapshot.gradle
     src/sample-portfolio/src/main/resources/application.yml
     src/sample-portfolio/src/test/resources/application-test.yml
  전부 / 그중 추적되지 않는 것(~) : 19 / 2
  그중 제한 패턴을 함께 언급하는 파일 : (0)

# 겹침을 보는 것이 있는가 — 규칙 이름을 쓰는 자리 전부와 공개 경로 스냅숏 게이트
src/adapter/inbound/web/src/main/java/dev/caskeleton/adapter/inbound/web/auth/RestrictedPathRule.java
src/adapter/inbound/web/src/main/java/dev/caskeleton/adapter/inbound/web/auth/SecurityConfig.java
src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverAdminPlaneConfiguration.java
  공개 경로 스냅숏 게이트가 있는 파일 : src/gradle/public-path-snapshot.gradle 
  그 게이트가 읽는 두 입력 :
File publicPathEnvironmentFile = rootProject.file('.env')
        rootProject.file('../docs/security/public-paths-snapshot.txt')
  값 쪽 입력이 추적되는가 : .gitignore:7:src/.env*	src/.env
  그 입력이 없으면 :
    if (!environmentFile.isFile()) {
        throw new GradleException(
                "missing public-path environment file ${environmentFile}")
  그런데도 그 태스크를 부르는 자리 :
.github/workflows/ci-quality-gates.yml:50:        run: ./gradlew check verifyPublicPathSnapshot verifyDependencyLocks --warning-mode=fail --no-daemon --stacktrace
.github/workflows/ci-quality-gates.yml:117:          verifyPublicPathSnapshot
.github/workflows/notification-platform.yml:97:          ./gradlew verifyEnvKeys verifyPublicPathSnapshot --console=plain
.github/workflows/web-release.yml:48:          verifyPublicPathSnapshot
  워크플로 전체에서 .env 를 입에 올리는 자리 전부 :
.github/workflows/fileserver-pr.yml:22:      - 'src/.env'
.github/workflows/notification-platform.yml:30:      - 'src/.env'
  그 게이트가 제한 패턴을 언급하는가 : 0
