refactor: 도메인 종류 판정을 긍정 목록으로 이관 - #120
Merged
Merged
Conversation
Four places asked whether a domain was not CUSTOM to decide whether the platform writes its A record, whether the row owes a record at all, which certificate backs it and which certRef the agent gets. That reads as "the platform serves every name in its own zone", which was true while it did. A kind that sits under a platform root and points somewhere else inherits all four answers wrongly, and the worst of them overwrites a user's own records with the proxy address. DomainKind now carries servedByPlatformProxy(), and the per-VM cap counts an enumerated set rather than everything that is not CUSTOM. certRefFor throws for a kind with no answer instead of falling through to the Let's Encrypt ref: that ref makes the agent drive certbot for the name, which for a name inside a platform root is the 2026-07-30 accident, and nginx -t accepts the result. DomainKindTest pins both answers per value and fails when a value is added without a decision. The unused findForMember query goes with it, since it carried the same shape of latent bug in a join nobody calls.
Two paths dereference a domain's VM without asking whether it has one, and both fail in a way that is worse than the missing value. The reservation sweep is the only thing that frees a reserved name, runs with no retries, and does not isolate its candidates. A notification for a row with no VM throws out of the loop, so one such row stops every later candidate for good: the name space quietly stops recycling and nothing says so. Each candidate is now contained and logged, and the notification step says at WARN that a kind without a VM has no recipient rule here rather than reaching a lookup that throws on a null id. requireRevivable read the held row's VM first, so a name held by something other than a VM answered a collision with a cross-user 500 instead of the 409 the unique index would give. Reversed, the held name simply loses. The sweep test spies the repository to make one candidate fail and asserts the other is still reclaimed; without the containment it fails.
Converting only the proxy-serving question left the reservation grace still spelled "not CUSTOM" in four places, and that is worse than before: a kind added now falls outside the per-VM cap and inside the grace, where the one test used to keep those two answers together. reservesNameAfterRelease() gives the second question its own home, the release path, the reserved-until reading, the expiry and the advance notice all ask it, and DomainKindTest pins three answers per value instead of two. The certRef note claimed more than the record supports. The 2026-07-30 review found the same outcome on the agent's side, before deploy, and closed it there by requiring an exact ref match; nothing shipped and nothing went unnoticed. What that refusal does not cover is this direction, where the ref is the recognised one and the name is wrong, and that is the reason the throw stays. managed() now says what it tests. It answers for the proxy's address record, not for every record the platform writes, and those stop being the same question the moment a name in our zone points somewhere else.
The reconcile scan is ordered and the cycle had no isolation, so a route that throws every time sits at the head of it and blocks every later route for good. This job is what eventually confirms a route nothing else retried, which makes the blockage silent as well as permanent. Same defect the reservation sweep had, and the same containment. The transport case keeps stopping the cycle on purpose: an unreachable agent says nothing about the route, and pushing the rest at an absent agent only spends the window. The resync loop is deliberately left whole and now says why. Its manifest is authoritative, so a name skipped there has its vhost pruned; aborting before the agent is called changes nothing, and that is the safe direction for it.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
📝 작업내용
DomainKind의 긍정 목록으로 이관servedByPlatformProxy()가 답하는 자리 넷: 플랫폼 A 레코드 소유, 행이 레코드를 빚졌다는 표시, 도메인을 받치는 인증서 조회, 에이전트 certRef 선택reservesNameAfterRelease()가 답하는 자리 넷: 해제 시 유예 부여, 예약 만료 표시, 만료 계산, 사전 통지SubdomainPolicy.CAPPED_KINDS열거 집합certRefFor는 답이 없는 종류에 Let's Encrypt ref를 주지 않고 예외 발생. 호출부는 JobRunr 잡 둘이라 요청 경로가 아님requireRevivable은 비교 방향을 뒤집어 이름 충돌에 409 응답findForMember쿼리 삭제⭐️ 검증
DomainKind값 3개, DB enum 3개,domains.vm_idNOT NULL로 확인💬 리뷰 포인트
servedByPlatformProxy()와reservesNameAfterRelease(),CAPPED_KINDS셋을 하나로 합치지 않은 것. 지금은 답이 같지만 존 안에 있으면서 프록시가 서빙하지 않는 종류에서 셋이 갈림certRefFor가 예외를 던지는 선택. 에이전트의 정확 일치 거부는 인식되는 ref를 틀린 이름에 주는 방향을 덮지 못함