제 6 장: 제어권 인계 프로토콜

본 장은 Handover_Policy(제어권 인계 정책)의 프로토콜 흐름을 정의한다. 3 종류 정책의 시맨틱, 인계의 원자성 보장, 타임아웃 롤백 및 알림 메커니즘을 포함한다. 본 장은 블루프린트 §2.4의 설계 의도에 대응한다.

6.1 적용 시나리오

제어권 인계는 여러 제어자가 동일한 Terminal_Resource를 순차 사용해야 하는 시나리오에서 발생한다. 본 사양은 두 종류의 핵심 인계를 정의한다:

  1. Fay-to-Fay: 한 Fay가 그 보유한 Session 제어권을 다른 Fay에 이전
  2. Fay-to-Human: Fay가 제어권을 인간 사용자에게 반환, 또는 인간 사용자가 제어권을 Fay에 위임

본 사양은 Fay-to-Fay를 주요 기술 대상으로 한다. Fay-to-Human 인계는 동일 흐름을 재사용하며, "인간 사용자"를 특수 인터페이스를 통해 단말 OS와 직접 상호작용하는 엔티티로 모델링한다.

6.2 인계 발동

6.2.1 발동 조건

인계는 다음 시나리오 중 하나에서 발동된다:

  1. 새 Fay에 의한 리소스 능동 요청: Fay-B가 AuthRequest를 통해 Fay-A가 점유한 리소스에 대한 접근을 요청
  2. 현 Fay의 능동 양도: Fay-A가 SessionTransferRequest를 통해 지정된 Fay-B에 제어권 양도를 능동 요청
  3. 관리 발동: 인간 사용자 또는 관리 인터페이스가 제어권 이전을 요구

6.2.2 인계 요청 메시지

SessionTransferRequest (body of ProtocolMessage) {
  required source_session_id : Session_ID                  // 현재 제어권 보유 Session
  required target_fay_id     : Fay_ID                      // 인수 대상
  required target_credential : CredentialContent           // 대상 측의 인가 자격 증명
  required handover_reason   : string
  optional handover_metadata : map<string, string>
}

단말은 인계 요청 수신 후 인계 평가 흐름을 시작한다.

6.3 인계 평가

단말은 다음 단계로 인계를 허용할지 평가한다:

6.3.1 단계 1: 소스 Session 검증

  • source_session_id에 대응하는 Session이 존재하고 active 상태인지 검증
  • 발신자가 본 인계를 요청할 권한을 가지는지 검증(해당 Session의 iFay_Runtime 또는 관리 권한을 가진 엔티티 등)

실패 → E_HANDOVER_INVALID_SOURCE 반환

6.3.2 단계 2: 대상 인가 검증

  • target_credential이 합법적인지 검증(제 3/4 장 규칙에 따라 완전 검증)
  • target_fay_id가 해당 리소스에 대해 원래 Session의 access_mode로 인가받았는지 검증

실패 → E_HANDOVER_INVALID_TARGET 반환

6.3.3 단계 3: 정책 평가

단말은 §6.4에 따라 Handover_Policy를 적용하여 인계를 허용할지 결정한다.

실패 → E_HANDOVER_REJECTED_BY_POLICY 반환

6.3.4 단계 4: 원자성 사전 점유

단말은 정책 평가 통과 후 즉시 원래 Session을 handover_pending으로 전환하여 사전 점유 상태에 진입한다:

  • 해당 리소스는 인계 완료 전에 다른 제어권 요청을 수락하지 않음
  • 원래 Session은 여전히 하트비트를 유지할 수 있음(active와 등가의 하트비트 요구사항 유지)이지만 새 리소스 작업을 발신할 수 없음

6.4 인계 정책 종류

Handover_Policy는 Resource_ID 입자도로 구성되며, 각 리소스는 MAY 다른 정책을 채택할 수 있다. 본 사양은 3 종류의 정책 종류를 정의하며, 모든 단말은 MUST 최소한 §6.4.1 우선순위 규칙 스크립트를 구현한다.

6.4.1 우선순위 규칙 스크립트(Priority Rule Script)

단말은 사전 정의된 규칙 스크립트에 따라 소스 Session과 대상 Fay의 우선순위 점수를 계산하며, 점수가 높은 쪽이 제어권을 획득한다.

구성 구조:

PriorityPolicyConfig {
  required policy_type : "priority_script"
  required script_id   : string                      // 단말 사전 구성된 스크립트 식별자
  optional parameters  : map<string, string>
}

평가 흐름:

  1. 단말은 script_id에 대응하는 규칙 스크립트를 로드
  2. 입력: 소스 Session 정보, 대상 Fay 식별자, 대상 자격 증명 grants, 현재 시각, Resource_ID 메타데이터
  3. 출력: 소스 우선순위 점수 S_source, 대상 우선순위 점수 S_target
  4. 결정: S_target > S_source → 인계 허용; 그렇지 않으면 거부

규칙 스크립트의 구체적 언어(CEL, Lua 서브셋, JSON Logic 등)는 구현이 선택하지만, MUST:

  • 결정적이다(동일 입력으로 동일 출력 생성)
  • 네트워크 또는 파일 시스템에 접근할 수 없다
  • 실행 시간은 100 ms 내로 제한된다

6.4.2 AI 모델 실시간 판정(AI Model Real-time Decision)

단말은 사전 통합된 AI 모델을 호출하여 현재 컨텍스트에 대해 실시간 판정을 수행한다.

구성 구조:

AIPolicyConfig {
  required policy_type   : "ai_model"
  required model_endpoint : string                    // 단말이 접근 가능한 모델 추론 엔드포인트
  optional context_fields : array<string>
  required decision_timeout_ms : uint32 (default 500)
}

평가 흐름:

  1. 단말은 결정 요청을 구축, source/target 정보와 context_fields로 지정된 컨텍스트를 포함
  2. AI 모델 추론 엔드포인트 호출
  3. 모델은 결정을 반환: allow / reject / require_human
  4. require_human 시 §6.4.3 인간 사용자 결정으로 디그레이드

단말은 MUST:

  • 강제 타임아웃 설정(기본 500 ms), 타임아웃은 reject로 처리
  • 고빈도 변동 회피를 위해 최근 결정 결과를 캐시(캐시 유효 기간 ≤ 5초)
  • AI 모델 사용 불가 시 우선순위 규칙 스크립트로 디그레이드

6.4.3 인간 사용자 결정(Human User Decision)

단말은 사용자 인터페이스를 통해 인간 사용자에게 결정을 요구한다.

구성 구조:

HumanPolicyConfig {
  required policy_type     : "human_decision"
  required ui_channel      : enum["system_dialog", "companion_app", "external_panel"]
  required decision_timeout_seconds : uint32 (default 30)
  required default_action  : enum["allow", "reject"]    // 타임아웃 시 기본 동작
}

평가 흐름:

  1. 단말은 ui_channel을 통해 인간 사용자에게 인계 요청 세부사항을 제시
  2. 사용자 입력 대기: 허용 / 거부
  3. 타임아웃 → default_action에 따라 처리

인간 결정 모드는 MUST:

  • UI에 명시적으로 표시: 소스 Fay 식별자, 대상 Fay 식별자, 리소스 식별자, 접근 모드, 인계 이유
  • UI에 민감 자격 증명 세부사항(서명, 키 ID 등)을 표시하지 않음
  • 기본 동작은 reject(보수적 보안)로 설정 권장

6.5 원자성 보장

인계는 MUST 원자성을 충족한다: 임의의 시점에 하나의 Resource_ID는 최대 하나의 활성 제어자를 가진다.

6.5.1 원자 시퀀스

단말은 MUST 다음 순서로 인계를 실행하며, 각 단계는 임계 영역 내에서 완료한다:

[T0] handover_pending 진입: 소스 Session 상태 전환, 리소스 사전 점유 진입
[T1] 소스 Session 종료: source_session_id 상태 active → terminating
                       OS 접근 제어 취소 발동
[T2] 리소스 회수 완료: source 상태 terminating → terminated
                     이 시점 리소스는 "활성 Session 없음" 상태
[T3] 대상 Session 생성: §5.2 흐름에 따라 대상 Fay를 위해 새 Session 생성
                       상태는 creating에 진입
[T4] OS 접근 제어 하달: 대상 Fay에 리소스 접근 활성화
[T5] 대상 Session이 active로 전환: 인계 완료

외부 관찰자가 [T2]와 [T3] 사이에서 관찰하는 리소스 상태는 "활성 Session 없음"이며, 두 Session이 동시에 활성인 상태는 관찰되지 않는다.

6.5.2 중간 단계 실패 시 롤백

[T0]–[T2] 중 임의 단계가 실패한 경우:

  • 소스 Session은 MUST active로 롤백
  • 리소스 회수 사전 점유 취소
  • E_HANDOVER_FAILED_AT_RELEASE 반환

[T3]–[T4]가 실패(소스는 종료되었지만 대상을 생성할 수 없음)한 경우:

  • 단말은 MUST 즉시 리소스를 정리(리소스는 "활성 Session 없음" 상태에 진입)
  • 원래 iFay_Runtime(소스 Session 종료됨)과 대상 iFay_Runtime(인수 실패)에 알림
  • E_HANDOVER_FAILED_AT_ACQUIRE 반환
  • 리소스 상태: 유휴 상태 유지, 후속 요청이 경합한다. MUST NOT 소스 Session을 자동 복구

6.5.3 동시 인계 요청의 직렬화

동일 리소스에 대한 여러 동시 인계 요청은 MUST 직렬화된다:

  • 리소스가 handover_pending 기간 중, 새 인계 요청은 큐에 입력되거나 거부됨
  • 구현은 MAY 거부(E_HANDOVER_IN_PROGRESS) 또는 큐 입력(최대 큐 길이 8)을 선택할 수 있음
  • 큐 입력된 요청은 FIFO로 처리되며, 각 요청은 이전 인계가 완료(성공 또는 실패)된 후에만 평가에 진입

6.6 타임아웃 처리

각 인계는 MUST 강제 타임아웃을 설정한다. 타임아웃 임계값은 정책 종류로 결정된다:

정책 종류기본 타임아웃범위
우선순위 규칙 스크립트1초0.5–2초
AI 모델 실시간 판정1초0.5–3초
인간 사용자 결정30초5–120초

6.6.1 타임아웃 롤백 원칙

인계가 타임아웃 내에 완료되지 않으면([T5]에 도달하지 않음), 단말은 MUST:

  1. 현재 진행 중인 인계 단계 중지
  2. [T0]–[T2]에 있는 경우(소스가 완전히 종료되지 않음): 원래 Session active 상태로 롤백
  3. [T3]–[T4]에 있는 경우(소스는 이미 종료됨): §6.5.2에 따라 처리(소스를 복구하지 않고, 리소스를 유휴로 둠)
  4. 관련 측에 HandoverFailedNotification 전송

6.6.2 타임아웃 알림

HandoverFailedNotification (body of ProtocolMessage) {
  required handover_id   : uuid
  required source_session_id : Session_ID
  required target_fay_id : Fay_ID
  required reason        : enum["timeout", "policy_rejected", "release_failed", "acquire_failed"]
  optional details       : map<string, string>
}

6.7 재시도 정책

인계 실패 후의 재시도 정책은 발신자가 결정한다. 본 사양은 재시도 메커니즘을 강제하지 않지만, 재시도의 경계를 정의한다:

  • 동일 인계 요청의 재시도 간격은 SHOULD ≥ 1초
  • 동일 (source_session, target_fay) 쌍의 재시도 횟수는 SHOULD ≤ 3회
  • 재시도는 MUST 새 handover_id를 사용한다

단말은 MAY 너무 빈번한 재시도 요청을 거부하고 E_HANDOVER_RETRY_LIMIT을 반환할 수 있다.

6.8 Fay-to-Human 인계

제어권을 인간 사용자에게 반환하는 인계는 위 흐름을 따르지만, 대상은 인간 사용자다:

  • target_fay_id 필드는 특수 값 "human:" + terminal_id를 사용한다(현재 단말의 인간 사용자를 의미)
  • 대상 인가 검증은 건너뛰어진다(인간 사용자는 자신의 단말 리소스에 대해 기본적으로 완전한 권한을 가짐)
  • 정책 평가는 통상 §6.4.3 인간 사용자 결정을 사용한다(인수 의향 확인)
  • 대상 Session 생성 시, Session은 어떤 Fay에도 바인딩되지 않고, OS 사용자 프로세스가 직접 보유한다

인간 사용자가 능동적으로 인수하는 역방향 흐름도 대칭이다: 원래 Fay Session을 인간이 보유하는 OS 프로세스로 대체.

6.9 인계의 가관측성

단말은 MUST 다음에 인계 이벤트의 가관측성을 제공한다:

수신자수신 이벤트
소스 iFay_RuntimeSessionStateChanged(active → handover_pending → terminating → terminated)
대상 iFay_RuntimeAuthResult(새 Session 생성 성공 응답) 또는 HandoverFailedNotification
단말 감사 로그완전한 인계 기록(handover_id, source, target, policy 결정, 최종 결과 포함)

6.10 인계 메시지 시퀀스

성공 인계의 완전한 메시지 시퀀스:

[Source Runtime]      [Target Runtime]      [Terminal]
       │                     │                  │
       │── SessionTransferRequest ─────────────→│
       │                     │                  │── 정책 평가
       │                     │                  │── source: active → handover_pending
       │←─ SessionStateChanged(handover_pending)│
       │                     │                  │── source 종료
       │←─ SessionStateChanged(terminating)─────│
       │←─ SessionStateChanged(terminated)──────│
       │                     │                  │── target 생성
       │                     │←─ AuthResult ────│
       │                     │                  │── target: creating → active
       │                     │←─ SessionStateChanged(active)

실패 롤백의 메시지 시퀀스:

[Source Runtime]      [Target Runtime]      [Terminal]
       │                     │                  │
       │── SessionTransferRequest ─────────────→│
       │                     │                  │── 정책 평가
       │                     │                  │── source: active → handover_pending
       │←─ SessionStateChanged(handover_pending)│
       │                     │                  │── 타임아웃 또는 실패
       │                     │                  │── source: handover_pending → active
       │←─ SessionStateChanged(active)──────────│
       │←─ HandoverFailedNotification ──────────│
       │                     │←─ HandoverFailedNotification