P25 첨부 목록 CSV와 일반 파일 경로 대조 실습 1.0.0

목표
CSV 행 ID와 attachment_path를 유지한 채 지정 root 아래의 해당 경로만 검사합니다.
기본 기대값은 present 1 / missing 1 / out_of_scope 1 / hold 0, 종료코드 1입니다.
본체 code/attachment_paths.py는 첨부 내용 열기·해시·외부 전송·이동·삭제를 하지 않습니다.
입력 목록 CSV는 읽고 SHA-256을 계산하며 새 JSON 보고서만 만듭니다.
가상 TXT 파일입니다. PDF나 다른 문서의 형식·내용·업무 적합성을 검사하지 않습니다.

환경
Python 3.12 이상, 표준 라이브러리만 사용합니다. 실제 확인 조합은 CPython 3.12.14 / Linux입니다.
Windows, macOS, 탐색기, Excel과 별도 문서 앱 동작은 실행하지 않았습니다.
다른 프로그램이 수정하지 않는 본인 소유의 격리된 로컬 연습 폴더만 사용하세요.
네트워크·동기화·여러 사용자가 수정하는 폴더나 출처 불명의 경로에는 적용하지 않습니다.

시작
ZIP을 새 빈 로컬 폴더에 풀고 이 README.txt가 있는 위치에서 명령을 한 줄씩 실행합니다.
python3가 같은 인터프리터를 가리킨다면 첫 단어만 python3로 바꿀 수 있습니다.
1. python --version
2. python tests/verify_checksums.py
3. python -m unittest discover -s tests -v
4. python tests/run_fixture_checks.py
5. python code/attachment_paths.py --input fixtures/main.csv --root fixtures/root --out main_result.json
6. python code/attachment_paths.py --input fixtures/normal.csv --root fixtures/root --out normal_result.json
7. python code/attachment_paths.py --input fixtures/duplicates.csv --root fixtures/root --out duplicate_result.json

각 출력 이름은 실행 전에 없어야 합니다. 출력 부모 폴더는 이미 있어야 합니다.
주 명령 5는 종료 1이며 완성 JSON을 만듭니다. 6은 종료 0, 7은 종료 1입니다.
종료 1 명령을 &&로 이어 쓰면 다음 명령이 실행되지 않을 수 있으므로 한 줄씩 실행하세요.
정상 시험 기대값은 53 tests / OK, fixture 검사 scenario_count=22 / all_passed=true입니다.
main_result.json에서 source_sha256과 root만 제외한 전체 내용은 expected/main.json과 같아야 합니다.
root는 실제 --root로 선택한 폴더의 절대 경로입니다. 선택한 폴더의 absolute 문자열과 별도로 대조하세요.
source_sha256은 읽은 입력 CSV 바이트의 SHA-256입니다. 첨부 내용 해시가 아닙니다.
summary의 present+missing+out_of_scope+hold는 input_rows와 같습니다.
duplicate_reference_rows는 이 행들에 붙은 보조 경고 수이며 입력 행 수에 더하지 않습니다.

주 입력
id,attachment_path
A01,docs/a.txt
A02,docs/missing.txt
A03,../private.txt

CSV 규칙
UTF-8 또는 시작 UTF-8 BOM, 쉼표, 정확한 id,attachment_path 헤더, 두 필드입니다.
CSV는 1,000,000바이트 이하, 데이터 1~10,000레코드, 파서 필드는 131072자 이하입니다.
ID는 ASCII 영문으로 시작해 영문·숫자·_·-만 1~32자, 대소문자 구분, 중복 금지입니다.
입력 CSV는 첨부 root 밖에 둡니다. 첨부 root는 --root로 반드시 직접 지정합니다.
경로 문자열은 POSIX 상대 표기 /만 사용, 1~240자·최대 20구성요소입니다.
절대·드라이브·역슬래시·콜론·빈구성요소·.·..·제어/모호한 Unicode·끝 점/앞뒤 공백을 거부합니다.
<>"|?*와 CON/PRN/AUX/NUL, COM/LPT 1~9·¹²³ 및 그 확장자도 보수적으로 거부합니다.
이 이름 규칙은 실습 계약이며 모든 운영체제의 이름 규칙을 재현하지 않습니다.
공백 제거나 Unicode/대소문자/경로 자동 정규화를 하지 않습니다.
문법 거부 경로는 out_of_scope로 남기고 해당 경로를 파일시스템에서 조회하지 않습니다.
링크/reparse·폴더·특수파일·하드 링크 첨부는 hold입니다. 링크 뒤 경로를 따라가지 않습니다.
원래 정확한 유효 경로가 같을 때만 중복 참조 그룹을 만들고 모든 ID를 남깁니다.
다른 이름의 내용 동일성이나 대소문자/Unicode 표현 간의 실제 동일 파일 판정은 없습니다.

종료코드와 복구
0: 새 완성 보고서, 모든 행 present이며 유효 경로 중복 경고 없음
1: 새 완성 보고서, missing/out_of_scope/hold 또는 중복 경고 있음
2: 옵션·구조·인코딩·접근·경로·저장 오류; 이번 실행의 새 최종 보고서 없음
기존 출력은 파일·폴더·끊어진 링크라도 보존합니다. 재실행은 main_second.json 같은 새 이름을 씁니다.
--out을 사용하며 셸 >로 기존 보고서를 덮지 마세요. CSV/root 안에는 결과를 만들지 않습니다.
중복 ID, 잘못된 헤더/열 수, 후반 CSV 구문 오류는 부분 행을 저장하지 않고 전체 중단합니다.
누락은 이 root와 검사 시점에서 못 찾았다는 뜻이며 PC 전체의 부재나 삭제를 뜻하지 않습니다.
권한 오류를 누락으로 숨기지 않고 전체 중단합니다. 보안 설정 변경이나 관리자 실행으로 우회하지 않습니다.
복구할 때는 제공 근거와 정확한 경로를 확인한 별도 사본으로 다시 검사합니다.

새 출력 보존
같은 출력 폴더의 새 임시파일을 끝까지 쓰고 fsync·JSON 재읽기 대조 뒤 os.link로 새 이름을 만듭니다.
로컬 파일시스템의 하드 링크 지원이 필요하며 실패하면 overwrite/replace로 바꾸지 않습니다.
처리된 저장 오류에서는 새 최종 보고서가 없습니다. 확정 뒤 임시파일 정리만 실패하면 완성 보고서를
보존하고 TEMP_CLEANUP_WARNING을 표시합니다. .p25-*.tmp는 최종 보고서가 아닙니다.
강제 종료·정전·공격자의 동시 경로 바꾸기는 보장하거나 시험하지 않았습니다.
Windows 재분석 지점은 반환된 메타데이터 분기를 확인하는 모의시험만 있으며 실제 앱 시험은 없습니다.

제공 파일
code/attachment_paths.py: 본문과 같은 전체 본체
fixtures/*.csv, fixtures/failures/*.csv: 같은 입력과 구조·인코딩 반례
fixtures/root/: 실제 TXT 세 개; docs/a.txt·docs/b.txt 각각 24바이트, unrelated.txt 37바이트
fixtures/manifest.json: 제공 fixture의 상대 경로·크기·SHA-256
expected/*.json, expected/hand_check.txt: 프로그램 출력에서 복사하지 않은 기준과 직접 검산
 tests/test_attachment_paths.py: 임시 사본에서 정상·경계·링크·저장 실패 등 53개 시험
 tests/run_fixture_checks.py: 같은 fixture의 CLI 22건을 독립 기대 JSON과 대조
 tests/verify_checksums.py, SHA256SUMS.txt: 제공 패키지 바이트 확인

체크섬과 원본 보존 시험은 가상 fixture의 바이트를 읽습니다. 이것은 첨부 대조 본체의 내용 미개방 검사와
별도의 패키지 검산입니다. 본체의 테스트에서는 attachment root 안 내용 open이 시도되면 즉시 실패합니다.
체크섬은 작성자 인증·악성코드 검사·내용 승인이나 원자적 파일 잠금이 아닙니다.
읽기 전용도 OS의 접근시각 갱신까지 막지는 않습니다. 이 실습은 보안 격리기나 백업 도구가 아닙니다.
