# 설계 문서가 쿼터를 무엇으로 정의하는가
  design-deviations.md:79  ### 9. Quota settlement is FIFO within a scope
  design-deviations.md:82  stragglers by TTL and the `STALE_QUOTA_RESERVATION` cleanup type rather than threading a reservation
  design-deviations.md:83  id through the upload session. `QuotaCommitGateway` therefore settles the oldest live reservation in
  design-deviations.md:86  Which row closes does not change any quota decision: enforcement sums reserved and committed bytes
  design-deviations.md:112  The metadata store, session store, quota service, queues, ledger, and staging locator carry
  design-deviations.md:145  | reserve quota + insert record + create session | a reservation that outlived a failed insert holds capacity for a file that never existed |
  design-deviations.md:146  | READY transition + quota commit | a finished file whose reservation was never converted holds capacity until the reservation expires |
  승인 컨트롤러의 클래스 설명 :
  DefaultTransferAdmissionController.java:1 package dev.caskeleton.application.fileserver.quota;
  DefaultTransferAdmissionController.java:2 
  DefaultTransferAdmissionController.java:3 import dev.caskeleton.application.fileserver.api.error.FileTooLargeException;
  DefaultTransferAdmissionController.java:4 import dev.caskeleton.application.fileserver.api.error.FileserverErrorCode;
  DefaultTransferAdmissionController.java:5 import dev.caskeleton.application.fileserver.api.error.FileserverFailureContext;
  DefaultTransferAdmissionController.java:6 import dev.caskeleton.application.fileserver.api.error.QuotaExceededException;
  DefaultTransferAdmissionController.java:7 import dev.caskeleton.application.fileserver.api.error.StorageFullException;
  DefaultTransferAdmissionController.java:8 import dev.caskeleton.application.fileserver.api.error.TransferAdmissionRejectedException;
  DefaultTransferAdmissionController.java:9 import dev.caskeleton.application.fileserver.api.metadata.QuotaScope;
  DefaultTransferAdmissionController.java:10 import java.util.Map;
  DefaultTransferAdmissionController.java:11 import java.util.OptionalDouble;
  DefaultTransferAdmissionController.java:12 import java.util.concurrent.ConcurrentHashMap;
  DefaultTransferAdmissionController.java:13 import java.util.concurrent.Semaphore;
  DefaultTransferAdmissionController.java:14 import java.util.concurrent.atomic.AtomicBoolean;
  DefaultTransferAdmissionController.java:15 
  DefaultTransferAdmissionController.java:16 /**
  DefaultTransferAdmissionController.java:17  * In-process admission control backed by bounded semaphores.
  DefaultTransferAdmissionController.java:18  *
  DefaultTransferAdmissionController.java:19  * <p>The failure vocabulary is deliberately distinct so a client can tell a permanent policy denial
  DefaultTransferAdmissionController.java:20  * from a transient one: an exhausted pool is {@code STORAGE_FULL}, a scope over its ceiling is
  DefaultTransferAdmissionController.java:21  * {@code QUOTA_EXCEEDED}, and momentary permit exhaustion is a retryable admission rejection.
  DefaultTransferAdmissionController.java:22  */
  DefaultTransferAdmissionController.java:23 public final class DefaultTransferAdmissionController implements TransferAdmissionController {
  DefaultTransferAdmissionController.java:24 
  DefaultTransferAdmissionController.java:25   private final TransferAdmissionProperties properties;
  DefaultTransferAdmissionController.java:26   private final StorageUsageProbe usageProbe;
  DefaultTransferAdmissionController.java:27   private final long maximumFileSize;
  DefaultTransferAdmissionController.java:28   private final Semaphore instanceUploads;
  DefaultTransferAdmissionController.java:29   private final Semaphore directDownloads;
  DefaultTransferAdmissionController.java:30   private final Map<String, Semaphore> scopeUploads = new ConcurrentHashMap<>();

# 승인이 실제로 검사하는 것
  DefaultTransferAdmissionController.java:44   @Override
  DefaultTransferAdmissionController.java:45   public TransferPermit acquireUpload(QuotaScope scope, long requestedBytes) {
  DefaultTransferAdmissionController.java:46     requireWithinFileSizePolicy(requestedBytes);
  DefaultTransferAdmissionController.java:47     requireBelowHardHighWater();
  DefaultTransferAdmissionController.java:48     Semaphore scopePermits = scopePermits(scope);
  DefaultTransferAdmissionController.java:49     if (!scopePermits.tryAcquire()) {
  DefaultTransferAdmissionController.java:50       throw new QuotaExceededException(
  DefaultTransferAdmissionController.java:51           "scope upload concurrency is exhausted",
  DefaultTransferAdmissionController.java:52           FileserverFailureContext.of(FileserverErrorCode.QUOTA_EXCEEDED, true));
  DefaultTransferAdmissionController.java:53     }
  DefaultTransferAdmissionController.java:54     if (!instanceUploads.tryAcquire()) {
  DefaultTransferAdmissionController.java:55       scopePermits.release();
  DefaultTransferAdmissionController.java:56       throw new TransferAdmissionRejectedException(
  DefaultTransferAdmissionController.java:57           "instance upload permits are exhausted",
  DefaultTransferAdmissionController.java:58           FileserverFailureContext.of(FileserverErrorCode.TRANSFER_ADMISSION_REJECTED, true));
  DefaultTransferAdmissionController.java:59     }
  DefaultTransferAdmissionController.java:60     return new SemaphorePermit(scopePermits, instanceUploads);
  DefaultTransferAdmissionController.java:61   }
  DefaultTransferAdmissionController.java:62 

# 예약이 저장되는 자리
  JpaFileQuotaService.java:36 
  JpaFileQuotaService.java:37   @Override
  JpaFileQuotaService.java:38   public QuotaReservation reserve(QuotaScope scope, long expectedBytes, Duration ttl) {
  JpaFileQuotaService.java:39     if (expectedBytes < 0) {
  JpaFileQuotaService.java:40       throw new IllegalArgumentException("expectedBytes must not be negative");
  JpaFileQuotaService.java:41     }
  JpaFileQuotaService.java:42     Instant now = clock.instant();
  JpaFileQuotaService.java:43     QuotaReservationEntity entity =
  JpaFileQuotaService.java:44         new QuotaReservationEntity(
  JpaFileQuotaService.java:45             UUID.randomUUID(),
  JpaFileQuotaService.java:46             scope.type(),
  JpaFileQuotaService.java:47             scope.value(),
  JpaFileQuotaService.java:48             expectedBytes,
  JpaFileQuotaService.java:49             now.plus(ttl),
  JpaFileQuotaService.java:50             QuotaReservationStatus.RESERVED.name(),
  JpaFileQuotaService.java:51             now);
  JpaFileQuotaService.java:52     return FileEntityMapper.toReservation(reservations.save(entity));
  JpaFileQuotaService.java:53   }

# 범위별 바이트 집계를 읽는 자리
  adapter/outbound/persistence-jpa · main · JpaFileQuotaService.java:84  public long reservedBytes(QuotaScope scope) {
  adapter/outbound/persistence-jpa · main · JpaFileQuotaService.java:89  public long committedBytes(QuotaScope scope) {
  adapter/outbound/persistence-jpa · postgresqlIntegrationTest · PostgreSqlFileserverMetadataStoreIntegrationTest.java:303  assertThat(quota.committedBytes(scope)).isEqualTo(600);
  adapter/outbound/persistence-jpa · postgresqlIntegrationTest · PostgreSqlFileserverMetadataStoreIntegrationTest.java:304  assertThat(quota.reservedBytes(scope)).isZero();
  adapter/outbound/persistence-jpa · postgresqlIntegrationTest · PostgreSqlFileserverMetadataStoreIntegrationTest.java:321  assertThat(quota.reservedBytes(scope)).isZero();
  adapter/outbound/persistence-jpa · postgresqlIntegrationTest · PostgreSqlFileserverReclamationIntegrationTest.java:291  assertThat(inTransaction(() -> quota.reservedBytes(scope))).isZero();
  adapter/outbound/persistence-jpa · postgresqlIntegrationTest · PostgreSqlFileserverReclamationIntegrationTest.java:292  assertThat(inTransaction(() -> quota.committedBytes(scope))).isEqualTo(600);
  adapter/outbound/persistence-jpa · postgresqlIntegrationTest · PostgreSqlFileserverReclamationIntegrationTest.java:307  assertThat(inTransaction(() -> quota.committedBytes(scope))).isEqualTo(450);
  adapter/outbound/persistence-jpa · postgresqlIntegrationTest · PostgreSqlFileserverReclamationIntegrationTest.java:323  assertThat(inTransaction(() -> quota.reservedBytes(scope))).isZero();
  adapter/outbound/persistence-jpa · postgresqlIntegrationTest · PostgreSqlFileserverReclamationIntegrationTest.java:324  assertThat(inTransaction(() -> quota.committedBytes(scope))).isZero();
  adapter/outbound/persistence-jpa · postgresqlIntegrationTest · PostgreSqlFileserverReclamationIntegrationTest.java:345  assertThat(inTransaction(() -> quota.committedBytes(scope))).isEqualTo(200);
  adapter/outbound/persistence-jpa · postgresqlIntegrationTest · PostgreSqlFileserverReclamationIntegrationTest.java:365  assertThat(inTransaction(() -> quota.committedBytes(scope))).isZero();
  application-core · test · FakeFileQuotaService.java:59  public List<Long> committedBytes() {
  application-core · test · FakeQuotaCommitGateway.java:24  public long committedBytes() {
  application-core · test · FinalizeUploadServiceTest.java:104  assertThat(quota.committedBytes()).isEqualTo(10);
  그중 정의 파일 밖의 main 호출자 : 0 줄
  [대조] acquireUpload 를 부르는 main 줄 : 2 줄

# 설정에 바이트 상한이 있는가
  application.yml:810   fileserver:
  application.yml:811     enabled: ${APP_FILESERVER_ENABLED:false}
  application.yml:812     destinations:
  application.yml:813       local-export:
  application.yml:814         provider-ref: local-primary
  application.yml:815         required-publication: unique-atomic-create
  application.yml:816         required-durability: file-and-directory-sync
  application.yml:817         maximum-rows: 1000000
  application.yml:818         maximum-encoded-bytes: 1073741824
  application.yml:819     providers:
  application.yml:820       local-primary:
  application.yml:821         # local-persistent is the only implemented/qualified R2 provider.
  application.yml:822         # shared-mounted/NFS and SFTP settings must not be added before their providers exist.
  application.yml:823         type: local-persistent
  application.yml:824         root-directory: ${APP_FILESERVER_LOCAL_ROOT:}
  application.yml:825         auto-create: false
  application.yml:826         strict-path-security: true
  application.yml:827         expected-file-store-name: ${APP_FILESERVER_LOCAL_EXPECTED_FILE_STORE_NAME:}
  application.yml:828         expected-file-store-type: ${APP_FILESERVER_LOCAL_EXPECTED_FILE_STORE_TYPE:}
  application.yml:829         mount-sentinel-name: .ca-fileserver-volume
  application.yml:830         mount-sentinel-sha256: ${APP_FILESERVER_LOCAL_MOUNT_SENTINEL_SHA256:}
  application.yml:831         expected-owner: ${APP_FILESERVER_LOCAL_EXPECTED_OWNER:}
  application.yml:832         maximum-root-mode: "0750"
  범위별 바이트 상한 이름이 나오는 자리 :
    fileserver 안에서 그 이름을 쓰는 자리 : 0 개
  승인이 읽는 프로퍼티 :
    :40  this.instanceUploads = new Semaphore(properties.instanceUploadPermits());
    :41  this.directDownloads = new Semaphore(properties.directDownloadPermits());
    :80  return used.isPresent() && used.getAsDouble() >= properties.softHighWater();
    :93  if (used.isPresent() && used.getAsDouble() >= properties.hardHighWater()) {
    :102  scope.canonicalKey(), ignored -> new Semaphore(properties.scopeUploadPermits()));
