확인 버튼을 누른 뒤 창이 사라졌는데 원래 작업은 아직 끝나지 않을 수 있다. 나는 이 차이를 독립 합성 상태로 설명하는 모듈을 만들었다. 실제 확인창에 답하는 기능은 없으며 브라우저와 연결하지 않는다. 이미 밖에서 관찰한 사실을 어떤 순서로 기록해야 완료라고 말할 수 있는지 읽는 예제다.
먼저 원래 작업의 문맥을 고정한다
Binding에는 합성 프로젝트·프로필·탭·문서 세대·작업·기대 조건의 별칭을 둔다. 다음 관찰은 이 묶음과 같아야 한다. 확인창을 처리하는 동안 다른 탭이나 계정으로 넘어간 결과를 원래 작업의 성공에 붙이지 않기 위해서다. 실제 이름, 주소, 원고나 계정은 쓰지 않았다. 기대 조건도 내용을 담은 문자열 대신 expected-a라는 합성 별칭이다.
이 묶음의 문자열이 같다는 사실은 실제 탭 소유권이나 계정을 인증하지 않는다. 신뢰할 호스트가 실제 관찰과 작업 기록을 연결한다는 전제다. 호출자가 임의로 상태 객체를 만들 수 있는 환경에서 이 데이터 클래스를 보안 경계로 쓰면 안 된다. 나는 관찰의 출처를 만들어 주는 코드와 관찰의 일관성을 읽는 코드를 구별했다.
응답 결과를 기록해도 창 닫힘은 아직 모른다
observe_dialog로 관찰한 ID를 연결하고 record_response_result에 외부 응답 결과를 기록한다. acknowledged는 정확한 참·거짓만 받는다. 이 함수에는 확인·취소 중 무엇을 고를지 정하는 인수가 없다. 응답을 보내지도 않으며 해당 경고가 자동 처리 가능한지 판정하지도 않는다. 이름 그대로 다른 계층에서 이미 시도한 응답의 관찰을 적는다.
응답이 확인됐어도 dialog_closed와 native_settled는 자동으로 참이 되지 않는다. 같은 응답 시도 기록을 다시 넣는 것도 거절한다. 확인창 ID가 달라지면 원래 기록을 갈아 끼우지 않는다. 이 계약은 재전송을 수행하는 구현이 아니라, 응답 한 번의 기록을 작업 완료로 확대하지 않게 하는 작은 상태 모델이다.
창과 명령의 수명은 따로 기록한다
record_closed는 해당 확인창이 닫혔다는 관찰이고 record_native_settled는 원래 네이티브 명령이 더 이상 미완료로 남아 있지 않다는 관찰이다. 어느 쪽이 먼저 기록돼도 다른 쪽을 대신하지 않는다. 둘을 모두 갖추기 전에는 record_original_outcome을 실행할 수 없다. 표시가 바뀐 직후 다음 입력을 허용하는 실수를 이 경계에서 드러내고 싶었다.
마지막에는 처음 묶어 둔 기대 조건이 실제로 맞았는지를 외부 검사기가 전달한다. False이면 미완료로 남고 읽기 확인을 다시 할 수 있다. True일 때만 verified가 참이 된다. 이 모듈은 텍스트·주소·상태 비교를 새로 구현하지 않는다. 기존 원래 결과 비교 모듈이 그 판단을 공급하는 데 참고가 될 수 있지만 두 모듈의 자동 연결은 구현하지 않았다.
코드와 구현 살펴보기
독립 합성 전체 구현
"""Synthetic in-memory observations. No browser, response or replay operation."""
from dataclasses import dataclass, replace
@dataclass(frozen=True)
class Binding:
project: str
profile: str
tab: str
document_epoch: int
operation: str
expectation: str
def __post_init__(self):
for value in (self.project, self.profile, self.tab, self.operation, self.expectation):
if not isinstance(value, str) or not value.strip() or len(value) > 80:
raise ValueError('Bounded nonempty synthetic aliases required')
if type(self.document_epoch) is not int or self.document_epoch < 0:
raise ValueError('Invalid document epoch')
@dataclass(frozen=True)
class Continuation:
binding: Binding
dialog_id: str = ''
response_recorded: bool = False
response_acknowledged: bool = False
dialog_closed: bool = False
native_settled: bool = False
verified: bool = False
stopped: bool = False
def _bound(state, binding):
if not isinstance(state, Continuation) or not isinstance(binding, Binding) or state.binding != binding:
raise ValueError('Changed operation context')
def _dialog(state, binding, dialog_id):
_bound(state, binding)
if not state.dialog_id or dialog_id != state.dialog_id:
raise ValueError('Stale or unobserved dialog')
def observe_dialog(state, binding, dialog_id):
_bound(state, binding)
if not isinstance(dialog_id, str) or not dialog_id.strip() or len(dialog_id) > 80:
raise ValueError('Invalid synthetic dialog identifier')
if state.verified or (state.dialog_id and state.dialog_id != dialog_id):
raise ValueError('Another continuation already recorded')
return replace(state, dialog_id=dialog_id)
def record_response_result(state, binding, dialog_id, *, acknowledged):
"""Record evidence from one external attempt; never choose or send a reply."""
_dialog(state, binding, dialog_id)
if state.stopped or state.verified or state.response_recorded:
raise ValueError('Cannot record another response attempt')
if type(acknowledged) is not bool:
raise ValueError('Explicit boolean evidence required')
return replace(state, response_recorded=True, response_acknowledged=acknowledged)
def record_closed(state, binding, dialog_id):
_dialog(state, binding, dialog_id)
return replace(state, dialog_closed=True)
def record_native_settled(state, binding):
_bound(state, binding)
return replace(state, native_settled=True)
def record_original_outcome(state, binding, *, matched):
_bound(state, binding)
if type(matched) is not bool:
raise ValueError('Explicit original-condition evidence required')
if state.stopped or not (state.response_recorded and state.dialog_closed and state.native_settled):
raise ValueError('Original continuation is not ready for verification')
if state.verified:
raise ValueError('Completed evidence cannot be rewritten')
return replace(state, verified=matched)
def stop(state):
return replace(state, stopped=True)
def summary(state):
if state.stopped: next_step = 'stopped_keep_record'
elif state.verified: next_step = 'complete'
elif not state.dialog_id: next_step = 'observe_dialog'
elif not state.response_recorded: next_step = 'await_explicit_external_response_result'
elif not state.dialog_closed: next_step = 'observe_dialog_closure_without_replay'
elif not state.native_settled: next_step = 'wait_for_original_native_settlement'
else: next_step = 'verify_original_expectation'
return {'next_step': next_step, 'complete': state.verified,
'response_acknowledged': state.response_acknowledged,
'dialog_closed': state.dialog_closed, 'native_settled': state.native_settled,
'operation': state.binding.operation, 'expectation': state.binding.expectation,
'automatic_replay': False, 'response_authorized_by_module': False}
늦은 관찰과 중지는 완료 기록을 속이지 않아야 한다
응답 확인이 없다는 사실 하나만으로 성공 처리하지 않는다. 다만 나중에 창 닫힘·원래 명령 종료·원래 기대 결과가 실제로 확인되면 재응답 없이 읽기 근거로 정리할 수 있도록 했다. 응답의 acknowledged 값은 거짓으로 남는다. 확인하지 못했던 응답을 나중에 성공 응답으로 바꿔 쓰는 대신 어떤 관찰로 결과를 확인했는지 구별하는 예제의 선택이다.
stop은 중지 표시만 남긴다. 원격 명령을 취소했다고 주장하거나 재개 권한을 만들지 않는다. 중지 뒤에 도착한 닫힘·명령 종료 관찰은 적을 수 있지만 미완료 작업을 새로 완료시키지는 못한다. 이미 끝난 결과는 중지했다고 과거 실패로 고치지 않는다. 이 작은 모델의 수명 정책이며 실제 제품의 전체 취소 처리를 이식한 것은 아니다.
다섯 기록으로 서로 다른 미완료를 읽는다
아래 선택기는 응답만 확인한 경우, 창은 닫혔지만 명령이 남은 경우, 명령은 끝났지만 기대 결과가 맞지 않는 경우, 원래 결과를 확인한 경우, 중지 뒤 늦은 종료 관찰의 다섯 기록을 읽는다. 모두 실제 Python 실행으로 만든 합성 자료다. 창이 움직이지 않아도 완료 여부가 왜 다른지 결과 필드로 비교할 수 있다.
초기 표시와 다섯 선택을 두 언어의 실제 인라인 스크립트로 실행해 저장 기록과 대조한다. DOM 대역에서 선택 연결을 확인하는 범위이며 실제 OS 확인창이나 브라우저 화면 검수는 아니다. 방문자의 페이지·파일·계정에 접근하거나 실제 응답을 보내는 기능도 없다. 이 구분을 선택기 가까이에 남겼다.
호스트와 이어 붙일 때 필요한 책임
16개 시험 메서드는 응답과 창 닫힘·명령 종료의 분리, 문맥의 각 필드 변경, 오래된 확인창 ID, 불일치와 늦은 결과, 중지, 중복 기록과 입력 보존을 다룬다. ZIP을 푼 별도 폴더에서도 같은 시험을 다시 실행한다. 구현·시험·기록 생성기·결과·사용법·MIT 라이선스를 함께 공개한다.
실서비스에 붙일 때는 실제 확인창의 출처와 권한, 민감한 경고의 사람 인계, 정확한 기대 조건 검사, 영구 기록과 시간 제한을 별도로 구현해야 한다. 패키지는 메모리의 합성 상태만 계산한다. 원래 브라우저나 고객 프로그램을 실행한 결과를 이 모듈의 시험으로 가져오지 않으며 새로운 브라우저 호환성도 주장하지 않는다.