다크모드
프로퍼티 점유 주체 연결 서버 계약
결론
Office → property_spaces → immutable property_space_aliases가 계속 정본이다. 이번 migration은 새 property, lease, room, occupancy 테이블을 만들지 않는다. 임대 유닛 키와 호스피탈리티 룸 ID를 기존 alias에 제한적으로 연결하고 배치 해석하는 RPC만 추가한다.
/contract 메뉴를 유닛별·멤버별 보기로 재구성하고 사업·차량 메뉴를 제거한 변경은 프런트 탐색 정리다. 기존 source key, route, relation projection과 서버 계약은 변경하지 않는다.
현재 데이터 경계
| 도메인 | 현재 실제 소스 | 영속성 | 이번 source key |
|---|---|---|---|
| 임대 정산·보증금 | useLeaseContracts.unit (301-1502 등) | mock | lease_unit_key |
| 계약·유닛·멤버 3축 | tenancyMock.unitKey (101 등) | mock | 같은 namespace의 별도 값; 자동 병합 금지 |
| 호스피탈리티 룸 | hospitality_rooms.property_space_id | server | 등록 시 hospitality_room_id alias도 고정 |
| 스테이 | hospitality_stays.property_space_id | server | 룸의 고정 공간 연결 재사용 |
| 예약 | hospitality_room_allocations.room_id | server | 배정된 룸의 직접 FK |
호스피탈리티 룸·예약·스테이는 direct FK 단계에 도달했다. hospitality_room_id alias는 legacy stay API와 감사 호환에 남지만 신규 룸 마스터 화면은 alias resolve로 공간을 추측하지 않고 저장된 property_space_id를 사용한다.
RPC
resolve_property_space_subject_links
입력은 Company, Office, lease_unit | hospitality_room, 최대 500개의 {sourceKey, propertySpaceId?}다. 이름·등록코드 비교 없이 해당 alias만 조회하며 입력 순서를 보존한다.
linked: 단일 alias가 허용 타입의 active 공간을 가리킴unresolved: alias 없음duplicate: 같은 입력 묶음에 source key 중복conflict: 기대 UUID 불일치, 비활성 대상 또는 허용 타입 불일치
임대는 unit만, 호스피탈리티는 guest_room | meeting_room | desk만 허용한다.
connect_property_space_subject
- Company·Office 관리자/운영자 또는 유효
property_admin만 실행한다. - 대상 공간은 입력 Company·Office의 active row여야 하며 도메인 허용 타입을 만족해야 한다.
- 새 alias 연결에는
expected_space_revision이 현재 revision과 같아야 한다. - 성공 시 기존
bind_property_space_alias_implcommand ledger를 사용해(company_id, request_key)멱등성을 보장한다. - 새 alias가 실제 삽입될 때만 공간 revision을 올리고
property.space_alias_bound감사를 남긴다. 같은 공간의 기존 alias 재요청은 revision을 다시 올리지 않는다. - 이미 다른 공간이 점유한 alias는
property_space_subject_conflict로 거부한다. - actor는 입력받지 않고
auth.uid()에서 유도한다.
보안
- public RPC는
security invoker, private 구현만security definer set search_path=''다. - private 구현은 기존
can_read_property_space_office/can_manage_property_space_office를 먼저 호출한다. - 새 public table이 없으며 기존 공간·alias RLS와 authenticated SELECT-only grant를 유지한다.
- 함수 실행권한은
PUBLIC/anon에서 회수하고authenticated,service_role에 명시 grant한다. - 다른 Office 공간 UUID를 쓰거나 다른 Company 사용자가 resolve하는 경로를 SQL smoke로 거부 검증한다.
Supabase의 2026-04-28 Data API 변경에 따라 function/table grant를 암묵 기본값에 의존하지 않는다. RLS와 Postgres grant는 별개 층으로 취급한다.
후속 occupancy 원장 조건
다음 조건이 충족된 뒤 도입한다.
- 서버 임대 계약 또는 서버 hospitality stay가 안정 UUID와 Company·Office 복합 scope를 가진다.
- 시작·종료 시각, 중첩 허용 규칙, 예약→스테이→체크아웃 상태머신이 확정된다.
- 동일 공간 동시 점유 허용 여부를 도메인별로 분리한다. 임대 계약과 시간제 회의실을 하나의 중첩 규칙으로 묶지 않는다.
- 관리비 부담자 원장과 hospitality 운영 점유를 동일 테이블로 합치지 않는다.
검증
- migration:
20260717094800_property_occupancy_subject_links.sql - SQL smoke:
supabase/tests/property_occupancy_subject_links_smoke.sql - JS:
propertyOccupancyLinks.spec.js,propertyOccupancyLinkRepo.spec.js,propertyOccupancyLinkMigration.spec.js