요약
factlog eject <하위경로>/<파일> 이 다른 디렉터리에 있는 동명 원본의 변환본까지 삭제한다.
경로로 지정했는데도 매칭이 basename 으로 퇴화한다. 원본은 남고 변환본만 사라지므로, 손대지
않은 소스가 조용히 추출 입력을 잃는다. 종료 코드는 0 이고 경고도 없다.
재현 (upstream/main c6d359d, macOS, pandoc 설치 환경에서 실측)
factlog init --target /tmp/kb && export FACTLOG_ROOT=/tmp/kb
mkdir -p /tmp/kb/sources/sub
printf '<html><body><p>top level</p></body></html>\n' > /tmp/kb/sources/report.html
printf '<html><body><p>nested</p></body></html>\n' > /tmp/kb/sources/sub/report.html
factlog ingest /tmp/kb/sources/report.html /tmp/kb/sources/sub/report.html --target /tmp/kb
# report.html -> runs/sources/report.html.md
# sub/report.html -> runs/sources/sub/report.html.md
factlog eject sub/report.html --target /tmp/kb # 중첩본만 지우려는 의도
factlog eject (KB: /tmp/kb): 2 matched source ref(s):
- runs/sources/report.html.md [on disk] <- 요청하지 않은 최상위본의 변환본
- runs/sources/sub/report.html.md [on disk]
runs/sources conversion(s) to delete: 2
factlog eject: 2 conversion(s) deleted, ... ; accepted.dl recompiled
결과: 변환본 두 개 모두 삭제. sources/report.html 원본은 그대로 남아 있으나 변환본이
없어져 추출 대상에서 빠지고, 그 소스가 뒷받침하던 사실은 다음 sync 에서 근거를 잃는다.
근거
provenance 헤더에는 디렉터리 정보가 이미 들어 있다(업스트림 #214 가 넣었다).
runs/sources/report.html.md <!-- ... | source: report.html | converter: pandoc | ... -->
runs/sources/sub/report.html.md <!-- ... | source: sub/report.html | converter: pandoc | ... -->
factlog/cli.py:2344 가 그 정보를 버린다.
conv_origin[ref] = PurePosixPath(origin).name # sub/report.html -> report.html
그리고 factlog/cli.py:2352-2356 의 경로 분기가 basename 끼리 비교한다.
if "/" in name:
# A path was given: the exact original is handled above; for a
# binary original also match the conversion it produced (by
# recorded origin). Same-basename files elsewhere are NOT matched.
return is_conv and conv_origin.get(ref) == np_.name
주석은 "Same-basename files elsewhere are NOT matched" 라고 계약을 명시하지만, 좌변은
:2344 에서 basename 으로 축소된 값이고 우변 np_.name 도 basename 이다. 양쪽에서
디렉터리가 지워지므로 경로 분기가 사실상 basename 매칭이고, 변환본이 미러링된
서브디렉터리(runs/sources/sub/)를 전혀 구분하지 않는다.
docs/reference/ignore-eject.md:41-43 도 "어간만 주면 같은 어간을 가진 모든 소스가
매칭된다" 고 적어, 경로 지정은 그보다 좁다는 것을 전제로 설명한다.
영향
- 심각도 높음. 사용자가 명시적으로 좁혀 지정했는데 요청하지 않은 파일이 삭제된다.
- 조용하다. 삭제 목록이 출력되긴 하지만 종료 코드 0 이고 "요청 범위를 넘었다" 는 신호가 없다.
- 복구가 자동이 아니다. 원본은 남아 있으므로 재-ingest 로 복구할 수 있지만, 사용자가
사고를 알아채야 가능하다.
- 서브디렉터리로 소스를 정리하는 KB(문서가 권장하는 구성)에서 동명 파일은 흔하다
(report.html, README.md, notes.md 등).
제안
conv_origin 에 원본 경로를 그대로 보관하고(또는 basename 과 함께), 경로 분기에서
디렉터리까지 비교한다. 변환본은 runs/sources/ 아래 원본의 서브디렉터리를 미러링하므로,
ref 의 미러 경로와 주어진 경로의 디렉터리가 일치하는지 확인하면 된다.
:2344 의 basename 축소가 필요한 곳은 orphan 재구성 경로이므로, 그쪽에서 필요할 때만 줄이고
저장은 원본 형태를 유지하는 편이 안전하다.
수용 기준
요약
factlog eject <하위경로>/<파일>이 다른 디렉터리에 있는 동명 원본의 변환본까지 삭제한다.경로로 지정했는데도 매칭이 basename 으로 퇴화한다. 원본은 남고 변환본만 사라지므로, 손대지
않은 소스가 조용히 추출 입력을 잃는다. 종료 코드는 0 이고 경고도 없다.
재현 (upstream/main c6d359d, macOS, pandoc 설치 환경에서 실측)
결과: 변환본 두 개 모두 삭제.
sources/report.html원본은 그대로 남아 있으나 변환본이없어져 추출 대상에서 빠지고, 그 소스가 뒷받침하던 사실은 다음 sync 에서 근거를 잃는다.
근거
provenance 헤더에는 디렉터리 정보가 이미 들어 있다(업스트림 #214 가 넣었다).
factlog/cli.py:2344가 그 정보를 버린다.그리고
factlog/cli.py:2352-2356의 경로 분기가 basename 끼리 비교한다.주석은 "Same-basename files elsewhere are NOT matched" 라고 계약을 명시하지만, 좌변은
:2344 에서 basename 으로 축소된 값이고 우변
np_.name도 basename 이다. 양쪽에서디렉터리가 지워지므로 경로 분기가 사실상 basename 매칭이고, 변환본이 미러링된
서브디렉터리(
runs/sources/sub/)를 전혀 구분하지 않는다.docs/reference/ignore-eject.md:41-43도 "어간만 주면 같은 어간을 가진 모든 소스가매칭된다" 고 적어, 경로 지정은 그보다 좁다는 것을 전제로 설명한다.
영향
사고를 알아채야 가능하다.
(
report.html,README.md,notes.md등).제안
conv_origin에 원본 경로를 그대로 보관하고(또는 basename 과 함께), 경로 분기에서디렉터리까지 비교한다. 변환본은
runs/sources/아래 원본의 서브디렉터리를 미러링하므로,ref의 미러 경로와 주어진 경로의 디렉터리가 일치하는지 확인하면 된다.:2344 의 basename 축소가 필요한 곳은 orphan 재구성 경로이므로, 그쪽에서 필요할 때만 줄이고
저장은 원본 형태를 유지하는 편이 안전하다.
수용 기준
eject sub/report.html이runs/sources/sub/report.html.md만 지운다.eject report.html(최상위 경로 지정)이sub/쪽을 건드리지 않는다.source:)를 가진 변환본도 종전대로 동작.