Files
Gnuboard7/tests/scenarios/attachment-orphan-cleanup.yaml
HeuJung cfc2caba1b feat(ckeditor5,board,ecommerce,page,core,admin_basic): 미참조 에디터 업로드 이미지 정리 신설 + 업로드 고아 동종 결함 7건 전수 해소
공개 (@Tuwasduliebst 제보) 대응.

- ckeditor5: 미참조 이미지 정리 엔진 신설 — 참조 판정(hash + 파일명 2토큰,
 filter_reference_sources 훅으로 확장 콘텐츠 등록) + 정리 커맨드(기본 꺼짐 옵트인,
 3중 게이트) + 업로드 관리 화면(권한 분리·참조 배지·개별/선택 삭제) + 기설치본
 설정 백필 upgrade step
- board: 임시 첨부 GC(상시) + 소프트삭제 첨부 영구 정리(옵트인) + 설정 UI,
 저장 payload 누락(F4)·빈 temp 디렉토리 잔존(F5) 수정
- ecommerce: 임시 상품 이미지 GC + 카테고리 삭제 시 이미지 파일 미삭제(S4) 수정
- page: 임시 첨부 GC
- 코어: 고아 첨부 정리 커맨드(옵트인, site_logo 보호) + 사이트 로고 교체 시
 구 로고 재편입 차단(S6) + DB 백업 미구현 501 정직화(B3) + 플러그인 관리자
 메뉴 설치·업데이트 자동 동기화(F1·F7)
- 동반: 플러그인 설정 검증 오류 필드명 라벨화(F3), Toggle 노드 type 정정
 수평 전개(marketing 1.0.2 bump·gdpr·ecommerce), ja 언어팩 4종, API 문서,
 hooks.md, 시나리오 매니페스트 8종, Playwright E2E 4종
2026-08-17 02:44:54 +09:00

80 lines
4.7 KiB
YAML

feature: 코어 고아 첨부 회수 (Orphan Attachment Cleanup)
description: |
폼 저장 전에 즉시 업로드되는 첨부(FileUploader 등)는 소유자 없이 먼저 만들어지므로,
폼을 저장하지 않고 이탈하면 파일과 기록이 그대로 남는다. 공개 이슈 #115 동류 결함.
판정식은 네 조건의 AND 다 — 하나라도 느슨해지면 현역 첨부가 지워진다.
1. 소유자(attachmentable_type/id)가 없다
2. 업로드 후 보존기간이 지났다 (폼 작성 중 유예)
3. 확장 소유가 아니다 (source_identifier 없음 — 확장 라이프사이클 소관)
4. 현역으로 쓰이는 첨부가 아니다 (현재 사이트 로고 등 보호 목록)
사이트 로고는 소유자 없이 설정값이 직접 참조하는 첨부라 판정식만으로는 고아와 구분되지
않는다. 그래서 설정에 실린 ID 를 보호 목록으로 삼으며, 그 해석은 정리 서비스 자신이
수행한다 — 호출자가 넘기는 값으로 두면 인자를 빠뜨린 호출 하나가 운영 중인 로고를 파기한다.
함께 고친 것: 로고 교체 시 구 로고가 설정에 재편입되던 결함. 종전 저장 흐름은 컬렉션
전체를 다시 수집했으므로(방금 올린 로고가 제출 payload 에 아직 없어서), 운영자가 화면에서
뺀 로고를 실제로 파기하지 않으면 교체할 때마다 파일·기록·설정 세 곳에 누적됐다.
지금은 이번 요청이 제출한 목록이 저장 대상을 정한다 — sub_flow `site_logo_replacement` 참조.
axes:
owner_state: [no_owner, has_owner]
source_owner: [core, extension]
age: [within_retention, past_retention]
protected: [not_protected, current_site_logo]
exclusions:
- { owner_state: has_owner, source_owner: extension, reason: "소유자가 있으면 source 와 무관하게 대상이 아니다 — 코어 소유 1건으로 충분" }
- { owner_state: has_owner, age: within_retention, reason: "소유자가 있으면 나이 판정에 도달하지 않는다" }
- { owner_state: has_owner, protected: current_site_logo, reason: "사이트 로고는 소유자 없이 설정이 직접 참조하는 첨부라 소유자 보유와 양립하지 않는다" }
- { source_owner: extension, age: within_retention, reason: "확장 소유는 나이와 무관하게 코어 GC 대상이 아니다 — 경과분 1건으로 충분" }
- { source_owner: extension, protected: current_site_logo, reason: "사이트 로고는 코어 소유 컬렉션이라 확장 소유와 양립하지 않는다" }
- { protected: current_site_logo, age: within_retention, reason: "보호 목록은 보존기간 경과분에서만 판정에 관여한다 — 미경과분은 나이에서 이미 제외" }
sub_flows:
- id: dry_run
description: 판정만 수행하고 파일·기록을 건드리지 않는 미리보기 경로
effects:
- core_orphan_prune_dry_run
- id: schedule_gate
description: |
자동 정리는 사용자 파일을 지우므로 기본 꺼짐이다. 커맨드가 `--scheduled` 에서
설정을 false 폴백으로 확인한 뒤에만 수행한다.
effects:
- core_orphan_prune_gate_off_by_default
- core_orphan_prune_gate_on
- id: site_logo_replacement
description: |
저장할 로고 목록은 이번 요청이 제출한 것이 정한다. 컬렉션 전체를 다시 훑으면 저장에
실패했거나 이탈로 남은 미참조 첨부가 설정에 되살아난다(브라우저 실측 — 신규 1건만
제출했는데 저장 결과가 2건).
effects:
- site_logo_replacement_drops_previous
- site_logo_unreferenced_collection_member_not_re_absorbed
- site_logo_stale_submitted_id_filtered
- site_logo_untouched_when_not_submitted
- site_logo_failed_save_keeps_removed_attachment
- id: settings_surface
description: 화면 바인딩 키가 코어 기본값·서버 검증과 일치하는지
effects:
- core_settings_orphan_toggle_default_off
- core_settings_orphan_keys_declared
effects:
- core_orphan_prune_skips_owned
- core_orphan_prune_skips_extension_owned
- core_orphan_prune_protects_site_logo
- core_orphan_prune_collects_replaced_site_logo
- core_orphan_prune_keeps_within_retention
- core_orphan_prune_deletes_file
- core_orphan_prune_deletes_soft_deleted
test_files:
- tests/Feature/Console/PruneOrphanAttachmentsCommandTest.php
- tests/Feature/Services/SettingsServiceSiteLogoTest.php
- templates/_bundled/sirsoft-admin_basic/__tests__/layouts/admin-settings-orphan-cleanup.test.ts
- templates/_bundled/sirsoft-admin_basic/__tests__/layouts/admin-settings-logo-remove-expression.test.ts
- tests/Playwright/specs/admin/settings-upload-orphan-cleanup.spec.ts