매번 다른 제목과 본문을 같은 절차에 넣는 규칙을 작은 모듈로 만들었다. 이 예제는 실제 입력을 수행하지 않고 실행할 자료의 모양만 만든다. 누락된 입력을 과거 값으로 채우지 않는지, 한글과 따옴표가 코드로 바뀌지 않는지 Python 진입점과 노트북에서 같은 함수로 확인할 수 있다.

정의와 이번 값을 서로 다른 문서로 둔다

recipe.json은 이름과 schema_version, parameters와 steps를 가진다. inputs.json에는 이번 제목과 본문만 둔다. compile_recipe는 두 자료를 받아 새 계획을 돌려준다. 단계 종류와 field는 정의에 고정돼 있고 value 문자열의 매개변수 자리만 이번 값으로 바꾼다.

원본 북극성 Recipe 전체를 복제한 모듈은 아니다. 여기서는 fill과 verify라는 두 단계 설명만 허용하고 실제 동작은 하지 않는다. 함수나 콜백도 받지 않는다. 절차의 틀과 내용 값을 분리하는 판단을 시험하는 예제이며 브라우저 드라이버와 호환되는 제품이라고 소개하지 않는다.

필요한 입력을 이번에 모두 받는다

parameters에 있는 키와 inputs의 키가 정확히 같아야 한다. 하나라도 빠지거나 알 수 없는 키가 있으면 CURRENT_INPUTS_REQUIRED다. 이름만 맞춘 입력을 받지 않도록 값의 문자열 형식과 크기도 확인한다. 이 예제에는 지난 제목과 본문을 기본값으로 저장하는 기능이 없다.

빈 문자열은 의도적인 이번 입력일 수 있으므로 그 자체를 금지하지 않는다. 하지만 본문이 비어도 되는 업무인지 판단하는 규칙은 따로 필요하다. 입력이 명시됐다는 것과 내용이 업무에 적합하다는 것은 다르다. 나는 구조 검사로 검증한 범위를 원고 품질의 판단으로 넓히지 않았다.

문자열을 한 번만 치환하고 다시 해석하지 않는다

정의에 있는 이중 중괄호 자리만 찾아 대응 값을 넣는다. 이번 입력 안에 다른 중괄호 문구가 있어도 두 번째 템플릿으로 처리하지 않는다. 따옴표와 역슬래시, 줄바꿈과 한글도 그대로 문자열에 남는다. JSON 텍스트를 코드처럼 이어 붙이거나 입력을 eval하는 경로는 없다.

정의의 잘못된 자리 표기와 선언하지 않은 매개변수는 거절한다. 치환한 뒤 지나치게 커진 값도 거절한다. 이것은 임의 HTML을 안전하게 렌더링하는 정화기가 아니다. 나중에 HTML이나 SQL 같은 다른 문맥으로 넘기려면 그 문맥에 맞는 처리가 추가로 필요하다. 자료로 보존하는 단계와 최종 표시의 안전성을 구별했다.

코드와 구현 살펴보기

독립 합성 전체 구현

"""Compile a narrow synthetic recipe to data. No browser or action executor."""
import hashlib,json,re
NAME=re.compile(r'[A-Za-z][A-Za-z0-9_]{0,40}')
SLOT=re.compile(r'\{\{([A-Za-z][A-Za-z0-9_]*)\}\}')
class PlanError(ValueError):pass

def exact(value,keys):
    if type(value) is not dict or set(value)!=set(keys):raise PlanError('INVALID_KEYS')

def compile_recipe(recipe,inputs):
    exact(recipe,['schema_version','name','parameters','steps'])
    if type(recipe['schema_version']) is not int or recipe['schema_version']!=1:raise PlanError('INVALID_VERSION')
    if not isinstance(recipe['name'],str) or not recipe['name'].strip() or len(recipe['name'])>80:raise PlanError('INVALID_NAME')
    parameters=recipe['parameters']
    if type(parameters) is not list or len(parameters)>20:raise PlanError('INVALID_PARAMETERS')
    if any(not isinstance(k,str) or not NAME.fullmatch(k) for k in parameters):raise PlanError('INVALID_PARAMETERS')
    if len(set(parameters))!=len(parameters):raise PlanError('DUPLICATE_PARAMETER')
    if type(inputs) is not dict or set(inputs)!=set(parameters):raise PlanError('CURRENT_INPUTS_REQUIRED')
    if any(not isinstance(v,str) or len(v)>20000 for v in inputs.values()):raise PlanError('INVALID_INPUT')
    steps=recipe['steps']
    if type(steps) is not list or not 1<=len(steps)<=20:raise PlanError('INVALID_STEPS')
    resolved=[]
    for step in steps:
        exact(step,['kind','field','value'])
        if step['kind'] not in ['fill','verify']:raise PlanError('UNSUPPORTED_STEP')
        if not isinstance(step['field'],str) or not NAME.fullmatch(step['field']):raise PlanError('INVALID_FIELD')
        text=step['value']
        if not isinstance(text,str) or len(text)>20000:raise PlanError('INVALID_TEMPLATE')
        remainder=SLOT.sub('',text)
        if '{{' in remainder or '}}' in remainder:raise PlanError('INVALID_TEMPLATE')
        def substitute(match):
            key=match.group(1)
            if key not in inputs:raise PlanError('UNDECLARED_PARAMETER')
            return inputs[key]
        value=SLOT.sub(substitute,text)
        if len(value)>20000:raise PlanError('EXPANDED_VALUE_TOO_LARGE')
        resolved.append({'kind':step['kind'],'field':step['field'],'value':value})
    raw=json.dumps(recipe,ensure_ascii=False,sort_keys=True,separators=(',',':'))
    return {'definition_hash':hashlib.sha256(raw.encode('utf-8')).hexdigest(),
            'steps':resolved,'compiled_only':True,'executed':False}

Python과 노트북의 실제 결과를 맞춘다

run_plan.py는 같은 JSON 문서를 읽어 compile_recipe를 호출한다. inspect.ipynb의 코드 셀도 같은 함수를 부른다. 시험은 CLI를 실제 하위 Python으로 실행하고, 이 패키지에서 직접 작성한 노트북 코드 셀을 Python으로 실행해 계획이 같은지 비교한다. 브라우저와 별도 주피터 커널을 띄우지는 않는다.

전체22개 시험은 입력 누락과 미지 키, 자료형과 중복 매개변수, 지원하지 않는 단계와 옛 ref, 잘못된 템플릿과 확장 크기 등을 다룬다. 입력을 바꿔도 정의 hash는 같은지, 정의를 바꾸면 hash가 달라지는지 확인한다. 실제 주피터 화면과 커널 프로토콜까지 검수했다고 보고하지 않는다.

다섯 계획 기록은 입력의 차이를 보여 준다

첫 번째 원고와 두 번째 원고, 문자 그대로 남긴 중괄호·한글, 입력 누락과 미지 키 거절을 실제 예제로 실행했다. 아래 선택기는 Python이 만든 다섯 합성 기록만 읽는다. fill과 verify가 계획 안에 보이더라도 방문자의 화면에 입력하거나 결과를 읽은 것은 아니다.

반환값에 compiled_only는 참이고 executed는 거짓이라고 명시한다. ZIP은 별도 폴더에 추출해 같은22개 시험을 실행하고 원본 파일 해시를 맞춘다. 한영 선택기의 기록 표시는 DOM 대역에서 확인한다. 코드 계획과 기록 뷰어, 실제 외부 실행을 다른 단계로 남겨 모듈의 완료 표시가 업무 완료처럼 보이지 않게 했다.

합성 로컬 기록의 읽기 뷰어

실제 실행은 새로운 계약으로 연결한다

전체 구현과 JSON 예제, Python 진입점과 노트북, 시험·기록 생성기·README를 수정하고 재사용할 수 있다. 표준 라이브러리만 사용하며 노트북은 이 패키지 위치를 작업 폴더로 해야 한다. 현재 함수가 파일이나 네트워크를 읽지 않는다는 범위와 예제 진입점이 JSON 파일을 읽는 범위도 구분해 설명했다.

현장 적용에는 실제 의미 대상의 새 관찰, 현재 계정과 권한, 동작 전달과 결과 확인, 중복 방지 원장이 필요하다. 이 모듈의 hash는 정렬된 JSON과 정확한 문자열의 정의 지문이며 승인과 안전성의 증거가 아니다. 나는 실제 일을 수행하는 층을 만들기 전에도 입력이 어떤 절차로 바뀌는지 독립적으로 검토할 수 있게 공유한다.

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