요약

  • draft-ietf-jmap-주체-history-00는Foo/get에 보존된 이전 및 파괴된 객체 버전을 추가하지만, 서버가 변경을 병합하고 어떤 순서로든 버전을 정리하며 저장 용량 압박 시 기록을 무통보 폐기할 수 있음을 명시적으로 허용한다.
  • 반환된 스냅샷은 복구를 지원할 수 있다. 그러나 행위자, 변경 요청, 인가 결정, 후속 결과를 식별하지 못하며,hasMoreHistory: false조차 추가 이력이 없음을 증명할 수 없다.

서버는 전화번호가 삭제되기 전의 연락처나, 파괴 직전 상태의 이메일을 반환할 수 있다. 이는 유용하다. 우발적 편집을 되돌릴 수 있게 하고, 별도의 프로토콜 없이도 사용자에게 “최근 삭제됨” 보기를 제공할 수 있다.

그러나 실제보다 권위적으로 보일 수도 있다. 응답에는 동일한 객체 ID, 순서화된 버전 번호, 해당 버전이 대체된 시각이 담긴다. 화면은 이 필드들을 깔끔한 타임라인으로 렌더링할 수 있다. 그러나 그 타임라인 어디에도 객체를 누가 변경했는지, 어떤 클라이언트가 변경 요청을 보냈는지, 어떤 인가 규칙이 이를 허용했는지, 후속 조치가 성공했는지를 밝히는 내용은 없다.

이 경계가 9월 15일 워킹 그룹 이름으로 업로드된JMAP 주체 History의 핵심이다. 이 제안은 이전 상태를 검색 가능하게 만든다. 복구 저장소를 감사 원장으로 바꾸지는 않는다.

프로토콜은 이벤트가 아니라 보존된 상태를 반환한다

JMAP 코어는 객체 유형별로Foo/get,Foo/changes,Foo/set,Foo/query,Foo/queryChanges메서드 계열을 제공한다. 현재 상태 동기화는 이미 아키텍처의 일부다.Foo/changes는 두 상태 사이에 어떤 객체 ID가 변경되었는지 알려줄 수 있지만 이전 값을 제공하지는 않는다.

새로운 기능인urn:ietf:params:jmap:주체-history는Foo/get을 확장한다.includeReplaced: true를 사용하면 응답에 라이브 객체와 함께 이전 버전을 포함할 수 있다.includeDestroyed: true를 사용하면 더 이상 존재하지 않는 객체도 포함할 수 있다. 두 플래그를 모두 사용하면 파괴된 객체에 대해 서버가 보존한 모든 버전을 반환할 수 있다.

각 버전은 동일한 객체 ID를 유지한다.objectHistory.version은 응답 내에서 한 객체의 버전 순서를 정한다.objectHistory.replaced는 해당 표현이 갱신 또는 파괴로 대체된 시점을 기록하며, null은 객체가 라이브임을 뜻한다.

이들은 상태 좌표이지 이벤트 출처가 아니다. 대체 타임스탬프는 이전 버전이 언제 생성되었는지 말해주지 않는다. 버전 번호는 트랜잭션을 지칭하지 않는다. 어느 필드도 행위자, 세션, 기기, 요청, 인가 정책, 승인, 동기, 알림, 외부 효과를 식별하지 않는다.

인접한 두 스냅샷만으로 관찰자는 값의 차이를 계산할 수 있다. 그러나 하나의 원자적 행위가 그 차이를 만들었다는 사실은 확립되지 않는다.

빠진 중간은 애초에 저장되지 않았을 수 있다

초안은 서버가 연속된 빠른 갱신을 하나의 버전으로 병합하는 것을 명시적으로 허용한다. 개별 변경마다 이력 기록을 만들 필요는 없다. 짧은 간격에 전화번호가 세 번 편집되었다면, 이력 표면에는 그 간격 이전의 값과 이후의 값만 남을 수 있다.

서버는 이전 버전을 임의의 순서로 정리할 수도 있다. 버전 시퀀스에 빈 구멍이 있는 것은 유효하다. 클라이언트는 이용 가능한 이력이 완전하거나 연속적이라고 가정해서는 안 된다. 따라서 누락된 버전에는 최소 두 가지 설명이 있다. 별개의 버전이 애초에 생성되지 않았거나, 버전이 존재했다가 나중에 제거되었다는 것이다.

버전 번호 자체도 요청 간에는 의도적으로 약하다. 서버는 일관된 버전 번호를 반환해야 하지만, 클라이언트는 이에 의존해서는 안 된다. 이 번호는 주로 하나의 응답 안에서 동일 객체의 버전을 정렬하기 위해 존재한다. 이를 내구성 있는 감사 이벤트 ID로 다루는 것은 사양이 약속하지 않는 동작에 의존하는 일이다.

이는 제안이 저장소에 대해 정직하게 만든다. 동시에 별도의 이벤트 시스템이 부족한 계약을 채워주지 않는 한 “발생한 모든 일을 보여줘”는 무효한 요청이 된다.

hasMoreHistory는 완전성 증명서가 아니라 검색 힌트다

historyLimit은 항목 총수를 제한하고 서버가 최신 버전을 반환하도록 지시한다. 요청된 ID 중 하나라도 한도에 도달하면hasMoreHistory: true는 더 오래된 보존 항목이 존재함을 클라이언트에 알린다. 다중 객체 응답은 어떤 ID에 더 많은 이력이 있는지 말하지 않으며, 클라이언트는 개별적으로 질의해야 한다.

true는 임시적이다. 초안은 다음 요청 전에 오래된 항목이 제거될 수 있음을 경고한다. 이 플래그는 예약이 아니라 서버가 한 시점에 검색 가능하다고 믿는 상태를 서술한다.

false는 더 약하다. 보통 요청된 ID에 대한 서버의 보존 이력 전부를 반환했다는 뜻이다. 그러나 효율적인 판단이 어려울 경우 서버는 더 많은 이력이 존재하더라도 false를 반환할 수 있다. false는 모든 변경이 캡처되었다는 뜻이 아니다. 아무것도 정리되지 않았다는 뜻도 아니다. 아직 존재하는 이전 표현이 모두 발견되었다는 보장조차 아니다.

대시보드가 false를 “완전한 감사 이력”으로 바꾸면 프로토콜의 의미를 뒤집는다. 방어 가능한 표현은 더 좁다. 이 응답에서 추가로 보존된 이력은 보고되지 않았다, 정도다.

null 기간은 무한 보존이 아니다

이 기능을 광고하는 계정은maxHistoryDuration을 노출한다. 숫자는 대체 이후 초 단위로 계산한 최대 연령이며, 그 이상의 버전은 폐기되었을 수 있다. null은 서버가 시간 기반 제한을 두지 않음을 뜻한다.

null은 영구 보존을 의미하지 않는다. 보안 섹션은 저장 용량 압박을 인정하고, 저장 한도에 도달하면 서버가 이력을 무통보 폐기할 수 있도록 허용한다. 기간은 보존 통제의 하나이며, 총 저장 용량은 또 다른 통제다. 어떤 객체 유형은 이전 버전을 전혀 유지하지 않으면서 이력 인터페이스를 지원할 수 있다. 이 경우 서버는 여전히 라이브 객체를objectHistory와 함께 반환하며, 모든 현재 객체 버전을 1로 표기할 수 있다.

따라서 이 기능은 인터페이스의 존재만 증명하며, 그 뒤에 최소한의 증거 보존 아카이브가 있다는 것을 증명하지 않는다. 조달 및 컴플라이언스 통제에는 자체적인 최소 보존 기간, 삭제 통보, 내보내기, 무결성, 법적 보존(legal hold) 요건이 필요하다.

복구와 책임 추적에는 서로 다른 영수증이 필요하다

복구 관점에서 제안된 표면은 잘 설계되어 있다. 클라이언트는Email/changes로 파괴된 ID를 확인하고, 이를Email/get에 넘겨 파괴된 객체를 요청한 뒤, 파괴 직전의 마지막 값을 표시할 수 있다. 사용자는 프로토콜이 삭제된 객체가 여전히 라이브라고 거짓말하도록 만들지 않고도 콘텐츠를 복원할 수 있다.

그러나 책임 추적 관점에서 동일한 응답은 불충분하다. 진지한 변경 영수증에는 인증된 행위자와 세션, 발신 클라이언트와 기기, 정확한 변경 본문, 조건부 상태 토큰, 적용된 인가 정책 버전과 결정, 서버가 수락한 트랜잭션, 변경 전후 해시, 내구성 있는 이벤트 ID, 알림 전송, 관찰된 결과가 필요하다.

객체 이력은 전/후 표현을 제공할 수 있다. 나머지 열은 채울 수 없다. 객체 소유자로부터 행위자를 추론하는 것은 안전하지 않다. 상태 전이의 성공으로부터 권한을 추론하는 것은 어느 정책이 적용되었는지를 무시한다. 저장된 값으로부터 후속 효과를 추론하는 것은 컨트롤 플레인의 수락과 실행 및 관찰을 혼동한다.

아키텍처는 책임 추적이 필요한 곳에서 복구 스냅샷을 추가 전용 감사 표면과 결합해야 한다. 호환되지 않는 가정 아래 하나의 저장소에 두 가지 역할을 강제해서는 안 된다.

이력 접근은 현재 접근과 단순히 같지 않다

이력은 어려운 인가 문제도 만든다. 초안은 현재 객체를 읽을 권한이 없는 사용자가 그 이력을 읽어서는 안 된다고 규정한다. 권한이 시간에 따라 변했다면, 서버는 요청자가 읽기 접근 권한을 가졌을 버전만 반환해야 한다.

이 규칙은 새로 권한을 부여받은 독자가 자신에게 권한이 없던 기간의 값을 보는 것을 막는다. 동시에 서버가 요청을 평가하려면 충분한 역사적 인가 맥락이 필요함을 의미한다. 현재 접근 통제 목록만으로는 이 질문에 답할 수 없을 수 있다.

JMAP Sharing은 이미 주체(principal), 의도된 권한 상태, 유효 접근을 분리한다. 주체 History는 여기에 시간적 차원을 더한다. 이전 버전을 열람할 권리는 현재shareWith맵이 아니라 역사적 권리에 의존한다. 이력 응답은 서버가 자체 평가에 따라 버전을 공개하기로 선택했다는 증거이며, 그 권한 결정이 별도로 기록되지 않는 한 완전한 기록이 아니다.

때로는 올바른 이력이 의도적으로 불완전하다

이 제안은 이전 객체가 사용자나 관리자가 제거하려 한 정보를 드러낼 수 있음을 인정한다. 삭제된 전화번호가 연락처 이력에 계속 보일 수 있다. 따라서 개인정보 보호나 컴플라이언스가 요구할 때 특정 객체의 이력을 소거할 수 있는 관리 기능을 권고한다.

이는 숨겨야 할 결함이 아니다. 복구, 책임 추적, 개인정보 삭제, 저장 경제성은 서로 다른 방향으로 당긴다. 쓸모 있는 제품은 데이터 클래스별로 어느 목표가 우선하는지, 누가 소거를 승인할 수 있는지, 변조 방지 소거 영수증이 남는지, 콘텐츠가 지워진 뒤에도 어떤 독립적 감사 사실이 남아야 하는지를 명시해야 한다.

위험한 약속은 삭제를 허용하도록 설계된 인터페이스 위에 얹힌 “불변 이력”이다. 똑같이 위험한 반응은 포렌식 원장이 아니라는 이유로 복구 기능을 끄는 것이다. 올바른 설계는 저장소를 분리하고 그 경계를 공표한다.

워킹 그룹 채택은 배포가 아니다

9월 문서는 3월에 발표된 개인 초안과 거의 텍스트가 동일하다. 프로토콜 본문에 새 근거가 생긴 것은 아니다. 의미 있는 변화는 이제 이 제안이 JMAP 워킹 그룹 이름을 달았다는 점이다.

Datatracker는 이를I-D Exists상태의 WG Document로 표시한다. 해당 페이지에는 책임 AD(지역 총괄), 셰퍼드, 텔레챗이 없다. 요약에는 의도된 상태가 비어 있지만 초안 헤더는 Standards Track으로 표기한다. 여전히 Internet-Draft이지 RFC가 아니며, 동결된 소스만으로는 어느 명명된 서비스에서도 가동 중인 구현이나 보존 정책이 확립되었다고 볼 수 없다.

이 성숙도 경계는 운영 교훈을 강화한다. 데이터 유형이 이력을 지원한다는 레지스트리 표기는 구현을 조율할 뿐이다. 어떤 버전이 캡처되고, 어떤 것이 병합되고, 무엇이 정리되었으며, 누가 인가되었고, 복원이 실제로 작동했는지는 가동 중인 증거만이 보여줄 수 있다.

출처