많은 제작기를 쓰려면 자료를 많이 모으는 것만으로 부족했다. 어느 소스의 어느 버전을 어디까지 읽었는지, 지금 글에 필요한 구간이 그 안에 있는지 확인해야 했다. 이 판단의 일부를 실제 고객 자료 없이 시험할 수 있는 작은 Python 모듈로 만들었다.

색인된 파일과 읽은 구간은 다른 값이다

source는 id·version·sha256·line_count라는 현재 자료의 선언을 받는다. review는 검토 당시의 같은 정보와 ranges를 받는다. 함수를 호출했다고 파일을 읽는 것은 아니다. 실제 파일 해시와 행 수를 계산하고 변경 불가능한 사본을 보관하는 일은 연결하는 도구의 책임으로 남겼다.

나는 이 범위를 일부러 좁혔다. 폴더에 파일이 있다는 사실이나 검색 결과에 등장했다는 사실은 지정 구간을 읽었다는 근거가 되지 않는다. 입력 자체가 거짓이면 결과도 믿을 수 없다. 이 모듈은 검토자의 선언을 비교하는 장부이며 읽기 행위와 내용 이해를 인증하는 프로그램이 아니다.

행 번호의 뜻을 먼저 고정했다

구간은 1부터 시작하고 끝 행을 포함한다. 각 구간은 순서대로 주며 겹치면 거절한다. 3–8행과 9–12행은 이어 붙여 4–10행의 요구를 충족할 수 있지만 9행이 비면 충족하지 않는다. 지정한 모든 필수 구간을 덮어야 이번 세부 설명의 근거 후보가 된다.

구간을 단순히 최솟값과 최댓값으로 합치면 중간에 읽지 않은 줄이 사라진다. 그래서 실제 연속 범위를 따라 cursor를 옮기며 빈 곳이 있는지 확인한다. 행 수를 세는 과정에서 겹치는 구간을 두 번 더하지 않도록 입력부터 거절한다. 합계가 커졌다고 검토 범위가 넓어지는 오류를 피하려는 규칙이다.

변경 뒤 옛 행 번호를 자동으로 물려주지 않는다

버전·해시·행 수 중 하나라도 다르면 freshness는 stale이 된다. 전체 파일을 읽었다고 선언한 옛 기록도 현재 전체 검토로 바뀌지 않는다. 해시만 같고 버전이 다를 때도 이 예제는 보수적으로 다시 확인할 대상으로 분류한다. 같은 파일을 잘못 연결한 경우는 SOURCE_MISMATCH로 거절한다.

이때 옛 사본에서 읽은 행 수는 reviewed_lines_in_snapshot으로 남긴다. 과거 검토의 존재를 지우지는 않되 현재 코드의 읽기 근거로 승격하지 않는 구조다. 변경된 파일의 몇 줄만 다시 읽어도 되는지는 사람이 차이를 보고 판단해야 한다. 해시를 새 값으로 바꾸는 행위만으로 검토를 갱신해서는 안 된다.

코드와 구현 살펴보기

독립 합성 전체 구현

"""Compare declared source metadata and reviewed line ranges; no file access."""
import re

class ReviewError(ValueError):
    pass

def _keys(value, fields):
    if not isinstance(value, dict) or set(value) != set(fields):
        raise ReviewError('INVALID_FIELDS')

def _text(value):
    if not isinstance(value, str) or not re.fullmatch(r'[A-Za-z0-9][A-Za-z0-9._-]{0,79}', value):
        raise ReviewError('INVALID_LABEL')
    return value

def _count(value):
    if type(value) is not int or value < 1 or value > 10000000:
        raise ReviewError('INVALID_LINE_COUNT')
    return value

def _hash(value):
    if not isinstance(value, str) or not re.fullmatch(r'[a-f0-9]{64}', value):
        raise ReviewError('INVALID_HASH')
    return value

def _ranges(value, total):
    if not isinstance(value, list) or len(value) > 10000:
        raise ReviewError('INVALID_RANGES')
    out = []
    last = 0
    for pair in value:
        if not isinstance(pair, list) or len(pair) != 2:
            raise ReviewError('INVALID_RANGE')
        start, end = pair
        if type(start) is not int or type(end) is not int or not 1 <= start <= end <= total or start <= last:
            raise ReviewError('INVALID_RANGE_ORDER_OR_BOUNDS')
        out.append((start, end))
        last = end
    return out

def _covers(known, required):
    for start, end in required:
        cursor = start
        for left, right in known:
            if right < cursor:
                continue
            if left > cursor:
                break
            cursor = max(cursor, right + 1)
            if cursor > end:
                break
        if cursor <= end:
            return False
    return True

def review_snapshot(source, review, required):
    """Inputs are caller assertions, not authenticated evidence or proof of reading.

    Line numbers are one-based and inclusive. Hash equality compares metadata;
    it does not read/hash files or validate their semantic content.
    """
    _keys(source, ['id', 'version', 'sha256', 'line_count'])
    _keys(review, ['source_id', 'version', 'sha256', 'line_count', 'ranges'])
    source_id = _text(source['id'])
    if _text(review['source_id']) != source_id:
        raise ReviewError('SOURCE_MISMATCH')
    version, reviewed_version = _text(source['version']), _text(review['version'])
    digest, reviewed_digest = _hash(source['sha256']), _hash(review['sha256'])
    total, reviewed_total = _count(source['line_count']), _count(review['line_count'])
    ranges = _ranges(review['ranges'], reviewed_total)
    needed = _ranges(required, total)
    if not needed:
        raise ReviewError('REQUIRED_RANGE_EMPTY')
    current = (version, digest, total) == (reviewed_version, reviewed_digest, reviewed_total)
    covered = current and _covers(ranges, needed)
    return {
        'source_id': source_id,
        'freshness': 'current' if current else 'stale',
        'requested_coverage': 'covered' if covered else ('missing' if current else 'unresolved_after_change'),
        'reviewed_lines_in_snapshot': sum(end - start + 1 for start, end in ranges),
        'whole_current_source_declared_reviewed': current and _covers(ranges, [(1, total)]),
        'eligible_as_current_source_note': covered,
        'proves_execution': False,
        'proves_semantic_truth': False,
        'publishes_content': False,
    }

통과 결과가 말하지 않는 것을 명시했다

eligible_as_current_source_note는 이번 필수 구간을 같은 자료 버전에서 검토했다고 선언한 경우에만 참이다. proves_execution과 proves_semantic_truth, publishes_content는 항상 거짓이다. 코드 구간을 읽었다는 기록으로 실제 프로그램 성공이나 의미적 사실 검증, 홈페이지 발행을 대신하지 않는다.

21개 시험은 인접 구간·빈 구간·틈·복수 요구·전체 범위·변경 해시·버전·행 수·잘못된 자료를 다룬다. 겹침과 역순, 참값을 행 번호로 넣는 경우, 범위 초과와 알 수 없는 필드도 거절한다. 입력 불변성과 실행 인증을 만들지 않는 반환값도 확인했다. 기능의 성공 경계와 거절 경계를 함께 시험했다.

아래 기록은 합성 실행에서 직접 만들었다

다섯 기록은 현재 일부 구간 충족, 필수 구간 미검토, 소스 변경, 전체 범위 선언, 다른 자료 ID 거절을 보여 준다. 실제 로컬 Python으로 생성했으며 선택기는 그 JSON을 읽어 표시한다. 방문자 파일을 읽거나 페이지에서 Python을 실행하는 기능은 넣지 않았다.

원본 폴더 시험 뒤 ZIP의 각 파일 해시를 대조하고 새 시험 폴더에 풀어 같은 21개 시험을 실행한다. 화면에 붙이는 두 언어의 인라인 선택 코드는 DOM 대역에서 기록과 대조한다. 이 확인을 실제 브라우저 렌더링이나 다른 프로그램의 소스 전수 검토로 소개하지 않는다. 시험 대상은 독립 합성 모듈이다.

합성 로컬 기록의 읽기 뷰어

글감 장부에 연결할 때 필요한 다음 단계

공개 ZIP에는 구현과 시험, 기록 생성기와 README를 함께 넣었다. 표준 라이브러리만으로 수정·재사용할 수 있다. 실무에서는 자료 수집기가 사본과 실제 해시를 만들고 편집자가 필수 구간을 정한 뒤 이 함수의 결과를 읽도록 연결하면 된다. 파일 경로나 고객 본문은 공개 입력에 넣지 않고 내부 ID로 연결할 수 있다.

이 도구는 어떤 구간이 글에 필요한지 고르지 못하며 기존 글과 의미가 겹치는지도 판단하지 않는다. 역사 시험·현재 소스·발행 확인은 별도 장부로 이어야 한다. 나는 자동화가 검토를 대신했다고 말하기보다, 어디까지 확인했는지 잊지 않게 하는 작은 도구로 공유하려 한다. 실제 자료를 연결할 때도 수집·판단·발행의 책임을 나눠 유지해야 한다.

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