CLINIC APPOINTMENT · POLICY FOUNDATION

병원마다 다른 예약 규칙을
재현 가능한 정책으로

tenant 기본값과 clinic override를 typed policy로 관리하고, Gateway 인증정보를 ActorContext로 고정하며, 의사결정 당시의 유효 스냅숏을 영구 보존한다.

FUTURE_ONLY Immutable Snapshot ActorContext CAS + Idempotency H2 · PostgreSQL · MySQL PLAN APPROVED · STEP 3-P PASS
규범 문서Markdown 설계
기준 커밋9008d3e에서 시각 이력 시작
표현 방식Hybrid · 기본 화면은 시뮬레이션
Related changePR #185

SIMULATION · DEFAULT VIEW

정책 변경은 초안에서 재현 가능한 스냅숏까지 이동한다

병원 운영자는 override를 바로 활성 설정으로 쓰지 않는다. compiler와 preview로 영향 범위를 확인하고, 승인과 CAS를 통과한 수정불가 스냅숏만 새 예약 의사결정에 사용한다.

01 · DRAFT

범위 내 편집

tenant baseline을 기준으로 clinic override를 작성한다.

02 · COMPILE

hard ceiling 검증

typed payload를 병합하고 완화할 수 없는 안전 규칙을 확인한다.

03 · PREVIEW

영향 설명

대상 예약과 제약 변화를 범위 제한 sample과 지표로 보여준다.

04 · APPROVE

권한과 revision 고정

필요한 승인 수와 actor scope를 만족하고 stale draft를 거절한다.

05 · ACTIVATE

스냅숏·head 전환

CAS로 generation을 올리고 수정불가한 유효 스냅숏과 outbox를 함께 기록한다.

NEW DECISION

새 예약 판단

항상 신뢰된 scope head의 최신 generation과 hash를 사용한다.

EXISTING HOLD

유효 hold

만료까지 기존 정책 스냅숏을 보호한다.

CONFIRMED

확정 약속

정책 활성화가 기존 고객 약속을 조용히 취소하거나 재작성하지 않는다.

Cache miss보다 stale cache가 위험하다.

activation event는 eviction 최적화일 뿐이다. effective read는 DB scope-head generation을 확인하며 DB 장애 시 오래된 스냅숏을 성공처럼 반환하지 않는다.

HISTORY · DECISION PROVENANCE

정책 값뿐 아니라 승인과 활성화 이유를 보존한다

이 HTML은 이해를 돕는 시각 동반 문서이며, 상세 계약과 인수 기준은 Markdown이 규범이다.

2026-07-27 · Issue #182에서 정책 기반 구현 범위 결정

Issue #182가 병원별 예약 정책을 재현 가능하게 만들 요구를 고정했다.

2026-07-27 · typed envelope + 수정불가 스냅숏 승인

규범 Markdown이 생명주기, approval, compiler, generation, cache, 오류 의미론을 정의했다.

01 · 문제와 결정

정책을 controller 분기나 nullable 설정으로 흩뜨리면 과거 결정을 설명할 수 없고, 동시 활성화와 병원별 override가 충돌한다.

REJECTED

정책별 정규화 table

SQL 무결성은 강하지만 kind마다 migration과 compiler branch가 반복된다.

REJECTED

단일 JSON document

확장은 쉽지만 lifecycle, revision, interval 유일성과 타입 검증이 약하다.

SELECTED

Envelope + typed payload

공통 동시성·감사는 정규화하고 payload는 Kotlin sealed type으로 검증한다.

불변 원칙

새 정책은 앞으로의 작업에만 적용한다. 이미 진행된 예약, hold, 확정 약속과 그 판단 근거는 수정하지 않는다.

02 · 인증 경계

Gateway는 인증하고 예약서비스는 업무 권한을 판단한다.

Client관리자·직원·고객
Gateway인증과 signed JWT 발급
Appointment APIsignature·issuer·audience 검증
ActorContextactor·tenant·clinic·patient scope
Domain Command권한·감사·상태 전이
신뢰하지 않는 값

request body의 actor type, role, tenant, clinic, patient, booking origin과 일반 X-User-* header는 권한 근거가 아니다.

보관하는 감사 근거

actor ID/type, issuer, token ID, 인증 시각, correlation ID, tenant/clinic scope.

보관하지 않는 정보

Bearer token 원문, 전체 JWT claims, parser detail, raw idempotency key, 불필요한 환자 개인정보.

구현 경계

현재 JWT 구현의 issuer/HMAC 검증을 required jti, audience, algorithm allowlist, assurance, actor type, patient subject, 복수 clinic scope까지 확장한다. 모순된 claim은 fail closed하며 실패 응답·로그는 token/claim/parser detail을 redaction한다.

03 · 관리자·고객 예약

예약 생성 출처는 body parameter가 아니라 ActorContext에서 파생한다. 이번 foundation은 규칙을 compile하고 실제 예약 상태 머신은 다음 issue가 구현한다.

관리자·직원 등록 ADMIN|STAFF → scope·capacity·동의 증빙 검증 → 정책이 허용하면 CONFIRMED
고객 등록 PATIENT → own-patient scope 검증 → PROVISIONAL → 관리자 승인 → CONFIRMED
승인 중 조건 변경 관리자가 시간·시술 조건을 바꾸면 바로 확정하지 않고 새 제안과 고객 동의를 요구한다.
확정 후 조건 변경 기존 확정은 유지하고 새 제안을 만든다. 고객이 새 조건에 동의한 뒤에만 변경을 확정한다.

가예약 자원 보장 정책

NO_HOLD

요청만 기록한다. 자원을 점유하지 않는다.

SOFT_HOLD

운영 후보로 표시하지만 충돌 방지 점유는 아니다.

HARD_HOLD

제한된 TTL 동안 실제 자원을 점유한다.

TTL 의미

가예약 요청 TTL 기본값은 24시간이며 5분~7일 범위다. HARD_HOLD만 1~30분의 별도 resource hold TTL이 필수이고 request TTL을 넘을 수 없다.

04 · 정책 모델

공통 envelope

tenant, scope, clinic, kind, version, schema version, lifecycle, effective interval, revision, payload hash, actor audit.

typed payload

BOOKING_COMMITMENT, hold/consent, capacity, reliability, reconfirmation, disruption, extension, SLA.

Override는 nullable이 아니다

INHERIT, SET(value), DISABLE을 명시한다. clinic은 platform·tenant hard ceiling을 완화할 수 없다.

Platformsafety guardrail
Tenantdefault policy
Clinictyped override
Compilerdeterministic merge
Snapshothash + source map

05 · Compile과 generation

동일 logical input은 map 순서와 관계없이 같은 스냅숏 hash를 만든다. 각 값의 출처와 policy version을 함께 보존한다.

시간 기준

DECISION_TIME은 hold·consent·proposal, SERVICE_TIME은 capacity·reconfirm·SLA에 사용한다.

Generation vector

{tenantGeneration, clinicGeneration}으로 clinic 변경이 tenant 전체 cache를 불필요하게 무효화하지 않게 한다.

Tenant preview의 O(1) 세대 검증

clinic override generation이 증가하면 같은 transaction에서 tenant head의 clinicGenerationEpoch도 증가한다. preview는 SHA-256(tenantId:epoch)를 고정하고 매 page마다 unique scope-head 한 행만 읽는다. 병원·예약 목록은 impact scan 입력이며 policy generation에는 포함하지 않는다.

시간 경계

cache bucket은 effective interval, scheduled activation, emergency expiration, clinic timezone의 DST gap/overlap 경계를 넘지 않는다.

Effective read API

decisionAtserviceAt은 offset 포함 RFC 3339 필수 query다. server-now default와 local date-time은 허용하지 않으며 두 값을 Instant로 정규화해 응답에 그대로 표시한다.

06 · 활성화 transaction

Validatetyped schema·ceiling
Previewrevision·generation pin
Approveactor·assurance evidence
CAS Activatehead·interval·generation
Outboxatomic event insert
  • validation과 canonical hash는 transaction 전에 수행한다.
  • head CAS, previous retirement, generation 증가, activation result, outbox insert는 한 transaction이다.
  • 범위 제한 idempotency key는 keyed hash만 저장한다. 같은 hash와 fingerprint는 기존 결과를 반환하며 raw key는 저장·로그·응답하지 않는다.
  • 다른 fingerprint, stale preview, interval overlap은 서로 다른 stable code로 거절한다.
  • 민감 정책은 approve와 activate 책임을 분리하고 작성자·승인자·활성자 직무분리를 검증한다.
  • interval overlap은 세 DB 모두 scope head lock 안에서 검사해 같은 conflict code로 매핑한다.
Scheduled activation 복구

DB time due scan, lease CAS, deterministic idempotency key, 범위 제한 retry와 startup catch-up을 사용한다. 60초 지연은 warning, 5분 지연은 MISSED critical이며 이전 active를 유지한 채 manual replay 또는 retire한다.

07 · 영속 구조

Table책임핵심 invariant
scheduling_policy_definitions공통 envelope와 typed canonical payloadscope/version 유일성, 불변 published payload
scheduling_policy_approvalsdraft revision별 승인 evidencestale revision과 동일 actor 이중 승인 거절
scheduling_policy_scope_headsscope-wide revision·generation과 tenant clinic epochtenant→clinic 잠금 순서, clinic generation과 tenant epoch의 원자적 증가
effective_scheduling_policy_snapshotscompiled payload와 source map불변 hash와 tenant/clinic 경계
scheduling_policy_activation_commandskeyed idempotency hash·due runner lease·retry동일 hash+fingerprint 재현, raw key 비보존, crash catch-up, MISSED 가시성
scheduling_policy_preview_jobsbounded async previewcursor·revision·generation·deadline checkpoint
scheduling_outbox_eventsgeneric aggregate eventV9 nullable aggregate 열 + nullable legacy planId, backfill/dual-write; 운영 parity 후 별도 V10 aggregate NOT NULL cutover

08 · 실패 의미론

Stable codeHTTPRetryable의미Caller action
POLICY_PAYLOAD_INVALID400falsetyped validation 실패payload 수정
POLICY_OVERRIDE_FORBIDDEN400falsehard ceiling 완화 또는 필수값 disableoverride 수정
POLICY_ACTOR_FORBIDDEN403falseactor scope 또는 명령 권한 없음권한·대상 확인
POLICY_RESOURCE_NOT_FOUND404falsepath scope와 resource 불일치 또는 없음tenant·clinic·ID 확인
POLICY_DRAFT_STALE409falseexpected revision 불일치최신 draft 재조회
POLICY_PREVIEW_STALE409falsepreview revision/generation 불일치preview 재실행
POLICY_ACTIVATION_CONFLICT409falsehead CAS 또는 interval 충돌최신 active head 재조회
POLICY_IDEMPOTENCY_CONFLICT409false같은 key의 다른 명령의도 확인 후 새 key 사용
POLICY_APPROVAL_INSUFFICIENT422false승인 수 또는 assurance 부족필요한 승인 수집
POLICY_PREVIEW_LIMITED429truetenant async queue 포화Retry-After 뒤 같은 요청 재시도
POLICY_ACTIVATION_MISSED409falsescheduled deadline 경과manual replay 또는 retire

모든 오류는 HTTP status, stable code, retryable, correlation ID와 caller/operator action을 제공한다. 같은 요청의 자동 재시도는 POLICY_PREVIEW_LIMITED만 허용한다. Preview는 동기 완료 시 200, async 전환 시 202와 jobId·Location·Retry-After를 반환하며 job endpoint에서 terminal status를 조회한다.

09 · 운영과 설명 기준

운영 증거

activation 결과·lateness·missed, compile latency, 신뢰된 generation read, stale cache rejection, preview deadline, outbox 상태를 metric과 structured log로 남긴다.

안전한 rollout

feature flag off → shadow compile → effective read/admin API → 후속 consumer 순으로 활성화한다.

Cache correctness

activation event는 eviction 최적화다. 모든 effective read는 DB scope head generation을 확인하며 DB 장애 시 stale 스냅숏을 반환하지 않는다.

Rollback과 forward repair

이전 compatible version을 새 activation으로 복구하고 스냅숏은 수정하지 않는다. destructive down migration과 DB 직접 수정은 금지한다.

기본 경보

scheduled lateness 60초/5분, outbox pending 60초·failed 지속, preview stale/deadline 5%, activation conflict 5%, 신뢰된 generation read failure 1%를 warning/critical 기준으로 삼고 tenant별 조정한다.

코드 설명 원칙

주석과 KDoc은 코드 문장을 반복하지 않는다. transaction 소유권, 상태 전이 거절 이유, CAS가 보호하는 invariant, hash 규칙, caller/operator action처럼 “왜 필요한가”를 설명한다.

10 · 범위와 후속 작업

THIS ISSUE

정책 기반 구현

typed policy, ActorContext, compiler, 스냅숏, 생명주기, approval, generation, preview, cache, generic outbox, admin API.

FOLLOW-UP

실제 예약 상태 머신

PROVISIONAL·HELD·CONFIRMED, 고객 동의, resource allocation, waitlist, disruption, overbooking.

기존 약속 보호

정책 활성화는 이미 진행된 시술·확정 예약·유효 hold를 조용히 취소하거나 재작성하지 않는다. 실제 안전 위반은 정책 변경이 아니라 disruption workflow로 처리한다.