# 능력 선언 — 제약을 평범한 문자열로만 담는 이유 *

Constraints are plain bounded strings on purpose. A capability record must stay serialisable * into a report and safe to publish through an actuator endpoint, so it never stores a provider * object — no {@code DataSource}, no {@code EntityManagerFactory}, no Hibernate {@code * SessionFactory}. Holding one would drag a live resource into a value type and let a report leak a * JDBC URL or credentials. */ public record CapabilitySupport( JpaCapability capability, SupportLevel level, List constraints) { # 컴팩트 생성자가 강제하는 것 public CapabilitySupport { Objects.requireNonNull(capability, "capability"); Objects.requireNonNull(level, "level"); constraints = List.copyOf(Objects.requireNonNull(constraints, "constraints")); for (String constraint : constraints) { if (constraint == null || constraint.isBlank()) { throw new IllegalArgumentException("capability constraint must not be blank"); } } } # 특권 리포트 — 담지 않는 것이 정의다 *

Deliberately narrow: the connected role, the schema search path, and two privilege answers. * There is no JDBC URL, no password, and no host — this record is designed to be safe to surface in * an actuator endpoint, and anything that would have to be masked before publishing does not belong * in it at all. */ public record DatabasePrivilegeReport( String currentUser, String searchPath, boolean canCreateInSchema, boolean canCreateInDatabase) { # 밖으로 나가는 리포트 — 담을 수 없는 것 /** * What the JPA platform is willing to say about itself over HTTP (design §37). * *

The interesting part of this type is what it cannot hold. There is no JDBC URL, no username, * no password, no SQL, and no entity catalog — an actuator endpoint is reachable by anyone who * reaches the management port, and every one of those would be a free reconnaissance answer. * *

What it does report is what an operator actually needs during an incident: which database * major version is behind this instance, which provider, which schema version, and whether the * safety properties this platform depends on are holding. # 그 리포트가 특권 리포트를 담는 방식과, 그 불리언이 실제로 계산하는 것 /** * Builds the report, deriving only bounded values from the privilege report. * *

The privilege report's {@code currentUser} and {@code searchPath} are deliberately reduced * to a single boolean here: an operator needs to know the runtime role passed verification, not * which role it is. openInViewDisabled, privileges != null && !privileges.holdsCreatePrivilege(), capabilities); # 역할 이름과 search_path 를 검사하는 정책의 main 호출자: 0 # 두 리포트의 컴포넌트 선언부에 있는 provider/자격증명 식별자 수 JpaPlatformReport.java: 0 DatabasePrivilegeReport.java: 0 # 그 리포트를 내보내는 엔드포인트 20:@Endpoint(id = "jpaplatform") 30: @ReadOperation @WriteOperation / @DeleteOperation / @Selector 매치: 0 * *

Read-only by construction: there is no write operation and no parameter. An endpoint that * could trigger a migration, a repair, or a cache eviction would be an admin capability reachable * over HTTP by whoever reaches the management port, which is exactly the boundary design §8.4 puts * those operations behind. # 다만 그 엔드포인트는 지금 노출되지 않는다 저장소에서 'jpaplatform' 이 나오는 곳: 1 JpaPlatformEndpoint.java:20:@Endpoint(id = "jpaplatform") 출하 설정의 노출 목록 258: include: health,prometheus,info,loggers,adapteractivation 259- # D2/D4/D5: explicitly excluded dangerous endpoints (env leaks secrets, 260- # heapdump/threaddump = memory forensics, shutdown = remote kill, configprops = secret leak). 등록 지점이 그것을 적어 둔다 * *

{@code @Endpoint} alone registers nothing; exposure is still governed by the application's * existing {@code management.endpoints} policy, which this deliberately does not widen.