북극성의 현재 탭과 명시적 목적지, 참고 탭을 나누는 판단을 독립 Python 모듈로 다시 만들었다. 실제 계정과 원고를 넣지 않고, 잘못된 대상을 골라 실행하기 전에 무엇을 확인할지 보여 주는 합성 예제다.

이 함수가 해결할 질문을 좁혔다

resolve_request는 신뢰할 작업 범위, 기존 탭 목록, 이미 해석한 의도를 받는다. 자연어에서 사이트를 완벽히 알아내는 기능은 없다. 사람이 카페 내용을 참고해 블로그에 쓰라고 요청했다면 해석 계층이 목적지와 참고를 나눈 뒤 이 함수에 전달해야 한다. 의미 해석과 결정 규칙을 한 함수의 성능처럼 합치지 않았다.

scope에는 profile·project·current 세 값만 둔다. intent는 destination·tab·references로 제한한다. 모르는 필드가 들어오면 거절하므로 execute 같은 새 키를 넣어 조작 권한을 만들어 낼 수 없다. 이 입력 계약은 새로 작성한 합성 계약이고 실제 제품의 모든 선택 기능을 그대로 옮긴 것이 아니다.

기본값과 명시한 선택의 순서를 정했다

목적지가 없으면 요청 당시 current 탭을 사용한다. 목적지를 지정했더라도 현재 탭의 platform과 같으면 그대로 쓴다. 다르면 같은 profile과 project 안에서 목적지 후보를 찾는다. 후보가 하나일 때만 선택하고 여러 개면 DESTINATION_AMBIGUOUS, 없으면 DESTINATION_NOT_OPEN으로 멈춘다. 첫 번째 항목을 임의로 고르지 않는다.

명시한 tab이 있으면 그 탭이 범위 안에 존재하는지 확인한다. destination까지 함께 주었는데 실제 선언된 플랫폼과 다르면 충돌이다. 여러 정보가 있다는 이유로 구체적인 입력 하나를 무시하지 않는다. 이 예제에는 새 탭 생성이나 계정 변경이 없으며 부족한 선택을 호출자에게 돌려주는 것이 정상 결과다.

참고 탭은 작성 위치를 바꾸지 않는다

references는 목적지 선택이 끝난 뒤 확인한다. 같은 범위에 있는 기존 탭만 허용하고 중복 참조나 작성 대상과 같은 참조를 거절한다. 반환값에는 target과 reference_tabs를 별도 필드로 둔다. 다음 단계에서 참고 자료를 읽더라도 target을 다시 마지막 선택 탭으로 바꾸지 않아야 한다는 계약이다.

반환값의 creates_tab과 acts_on_page는 모두 거짓이다. 자료를 정리하는 순수 함수여서 입력 딕셔너리와 목록도 바꾸지 않는다. 이 값을 받았다고 바로 실제 브라우저를 조작해도 된다는 뜻은 아니다. 호스트에서 실제 탭 소유권과 로그인, 현재 문서 상태를 다시 확인하는 실행 계층이 필요하다.

코드와 구현 살펴보기

독립 합성 전체 구현

"""Independent synthetic request routing. No browser, account or filesystem access."""
from dataclasses import dataclass

class ContextError(ValueError):
    def __init__(self,code):self.code=code;super().__init__(code)

def exact(value,keys,code):
    if not isinstance(value,dict) or set(value)!=set(keys):raise ContextError(code)
def text(value):
    if not isinstance(value,str) or not value or value!=value.strip() or len(value)>80:raise ContextError('INVALID_TEXT')

def resolve_request(scope,tabs,intent):
    """Resolve declared intent once. This does not infer intent from natural language."""
    exact(scope,['profile','project','current'],'INVALID_SCOPE')
    for v in scope.values():text(v)
    exact(intent,['destination','tab','references'],'INVALID_INTENT')
    if not isinstance(tabs,list) or not 1<=len(tabs)<=100:raise ContextError('INVALID_TABS')
    if not isinstance(intent['references'],list) or len(intent['references'])>10:raise ContextError('INVALID_REFERENCES')
    for field in ['destination','tab']:
        if intent[field] is not None:text(intent[field])
    for ref in intent['references']:text(ref)
    ids=[]
    for t in tabs:
        exact(t,['id','profile','project','platform'],'INVALID_TAB')
        for v in t.values():text(v)
        ids.append(t['id'])
    if len(set(ids))!=len(ids):raise ContextError('DUPLICATE_TAB')
    allowed=[t for t in tabs if t['project']==scope['project'] and t['profile']==scope['profile']]
    byid={t['id']:t for t in allowed}
    current=byid.get(scope['current'])
    if not current:raise ContextError('CURRENT_TAB_OUTSIDE_SCOPE')
    explicit=intent['tab'];destination=intent['destination']
    if explicit:
        target=byid.get(explicit)
        if not target:raise ContextError('TARGET_OUTSIDE_SCOPE')
        if destination and target['platform']!=destination:raise ContextError('DESTINATION_CONFLICT')
        reason='explicit-existing-tab'
    elif not destination or current['platform']==destination:
        target=current;reason='request-time-current-tab'
    else:
        matches=[t for t in allowed if t['platform']==destination]
        if not matches:raise ContextError('DESTINATION_NOT_OPEN')
        if len(matches)>1:raise ContextError('DESTINATION_AMBIGUOUS')
        target=matches[0];reason='unique-existing-destination'
    refs=intent['references']
    if len(refs)!=len(set(refs)):raise ContextError('DUPLICATE_REFERENCE')
    if any(x not in byid for x in refs):raise ContextError('REFERENCE_OUTSIDE_SCOPE')
    if target['id'] in refs:raise ContextError('TARGET_IS_REFERENCE')
    return {'scope':{'profile':scope['profile'],'project':scope['project']},'target':target['id'],'reference_tabs':list(refs),'reason':reason,'creates_tab':False,'acts_on_page':False}

성공보다 잘못된 선택을 거절하는 시험을 늘렸다

18개 시험은 현재 탭, 명시 목적지, 참고 탭 이동, 같은 플랫폼에서 현재 탭 우선, 모호한 후보와 없는 후보를 다룬다. 다른 프로필의 탭과 목적지 충돌, 중복 ID, 오래된 current, 범위 밖 참고와 중복 참고도 확인했다. 문자열 대신 참값을 넣는 경우와 알 수 없는 필드, 입력 불변성도 따로 검사한다.

원본 폴더에서 통과한 뒤 공개 ZIP의 바이트 해시를 대조하고, ZIP을 새 G드라이브 시험 폴더에 풀어 같은 18개 시험을 실제 실행했다. 이 과정은 합성 규칙의 검증이다. 실제 사이트에서 계정을 바꿨거나 사용자의 작성 중인 원고를 시험 대상으로 삼은 결과는 아니다.

아래 화면은 실행 결과를 읽는 선택기다

다섯 상황의 records는 실제 로컬 Python 실행으로 만들었다. 현재 탭 유지, 참고와 목적지 분리, 모호한 목적지, 다른 범위, 없는 목적지의 결과를 선택해 읽을 수 있다. 이 프레임 안에서 Python이나 브라우저 제어 코드를 실행하는 것은 아니다. 방문자의 계정과 파일도 읽지 않는다.

선택기가 표시하는 값과 ZIP의 records를 대조하고 두 언어의 실제 인라인 선택 코드를 DOM 대역에서 실행해 확인한다. 이것을 실제 브라우저 렌더링이나 키보드 접근성 검수라고 부르지 않는다. 읽기 뷰어의 확인과 모듈의 Python 시험은 서로 다른 증거이며 함께 제공한다.

합성 로컬 기록의 읽기 뷰어

자기 업무에 붙일 때 바꿔야 할 부분

ZIP에는 전체 구현, 시험, 실행 기록 생성기와 README를 넣었다. Python 표준 라이브러리만 사용하며 수정하고 재사용할 수 있다. profile과 project는 인증된 호스트에서 가져와야 하고, 플랫폼 라벨은 실제 주소를 검증한 값이 아니다. 탭 목록을 외부 입력 그대로 믿는다면 이 함수만으로 잘못된 대상 실행을 막을 수 없다.

여러 계정을 허용하는 시스템은 이 예제의 단일 profile 범위를 확장하면서 실제 권한 검사를 별도로 설계해야 한다. 문맥을 고른 뒤 실행 전까지의 변경도 다시 확인해야 한다. 선택 결과를 보관하는 기능과 실제 작업의 원자성·지속 저장·로그인 유효성은 구현하지 않았다. 재사용할 때 필요한 다음 질문을 코드와 함께 남겼다.

코드·시험·실행 기록 ZIP 받기
Python 표준 라이브러리 · 합성 자료 · 수정과 재사용 가능