targets 에 명시된 {modules,plugins,templates,lang-packs}/_bundled 가
상위 protected(modules 등)에 걸려 3-way apply 목록에서 제외되던 결함 수정.
targets 가 protected 보다 더 구체적이면 오버라이드해 통과시켜, 코어 업데이트로
번들 확장 갱신 파일(composer.json·vendor-bundle.json 등)이 정상 반영되도록 한다.
자동 발견 폴백의 방어 및 prune 롤백 경로는 불변.
아울러 이슈 템플릿의 사용법 문의 링크를 Discussions 에서 sir.kr Q&A 로 전환.
113 lines
10 KiB
YAML
113 lines
10 KiB
YAML
# audit:allow test-scenario-coverage reason: 본 매니페스트는 활성 디렉토리 보존 + 단발성 복구 인프라의 시나리오 매트릭스 SSoT. 핵심 회귀 가드는 test_files 의 통과 테스트로 커버.
|
|
|
|
feature: 코어 업데이트 활성 확장 디렉토리 보존 + _bundled 사용자 추가 항목 보존 + beta.5 단발성 복구
|
|
|
|
description: |
|
|
beta.4 의 `CoreUpdateService::applyDiscoveredTopLevelPaths` 가 `isCoveredByApplied`
|
|
단방향 검사 결함으로 `applied=['templates/_bundled']` 일 때 source 의 `templates/`
|
|
자체를 신규 항목으로 오인하여 `copyDirectory(removeOrphans:true)` 호출 → base 의
|
|
활성 서브디렉토리(`templates/sirsoft-basic` 등) 가 orphan 으로 판정되어 삭제되던
|
|
회귀 의 차단 + beta.4 → beta.5 1회성 자동 복구 인프라.
|
|
|
|
근본 수정 (3건 trade-off 없는 구성):
|
|
1. `isCoveredByApplied` 양방향 검사 — applied 가 path 의 자식일 때도 cover 판정.
|
|
2. `applyDiscoveredTopLevelPaths` 의 `copyDirectory` 호출에서 `removeOrphans:false`
|
|
— 자동 발견 폴백은 destination 의 orphan 정리 책임 없음.
|
|
3. `update.protected_paths` 기본값에 `modules,plugins,templates,lang-packs` 추가
|
|
— 부모 디렉토리 단위 자동 발견 자체를 방어 깊이로 차단.
|
|
|
|
단발성 복구 인프라 (`Upgrade_7_0_0_beta_5`):
|
|
1. `recoverActiveExtensionDirs` — DB active row 식별자 + `_bundled/{id}` 매칭
|
|
→ 활성 디렉토리 부재 시 `FilePermissionHelper::copyDirectory(removeOrphans:false)`
|
|
복사. 4개 도메인 (modules/plugins/templates/language_packs) 동일 패턴.
|
|
2. 외부 확장 (DB active + `_bundled` 부재): 자동 복구 불가 → stdout 안내 +
|
|
DB row `status='inactive' + deactivated_reason='extension_dir_lost_beta5'`
|
|
단발성 전환. `deactivated_*` 컬럼 부재 시 graceful skip.
|
|
3. `recoverPendingStubFiles` — `{type}/_pending/{.gitignore,.gitkeep}` 부재 시
|
|
재생성 (기존 stub 은 덮어쓰지 않음).
|
|
4. `verifyBundledLangPacksFallback` — beta.4 의 `recoverLangPacksBundled` 와
|
|
동등 fallback 을 무제한 깊이 재귀로 한 번 더 멱등 실행.
|
|
|
|
axes:
|
|
source_structure: [bundled_only, bundled_plus_other, empty_top_level] # source 의 templates/ 디렉토리 구조
|
|
applied_targets_set: [bundled_targets, no_targets, mixed_targets] # config('app.update.targets')
|
|
active_subdir_state: [present, absent, empty] # base 의 활성 서브디렉토리 존재 여부
|
|
protected_paths_set: [defaults_with_parent, defaults_without_parent] # protected_paths 에 부모 디렉토리 포함 여부
|
|
domain: [modules, plugins, templates, language_packs]
|
|
bundled_presence: [present, missing] # _bundled/{id} 존재 여부 (단발성 복구)
|
|
source_url_column: [present_value, present_null, column_absent] # 외부 확장 안내 분기
|
|
deactivated_columns: [present, absent] # 마이그레이션 적용 여부 (graceful skip)
|
|
pending_stub_state: [both_present, gitignore_missing, gitkeep_missing, both_missing, dir_missing]
|
|
lang_packs_pending_depth: [depth_2, depth_3, depth_5, depth_unbounded] # locatePendingLangPacksSourceDeep
|
|
user_added_bundled_entry: [dir_present, file_present, absent] # _bundled/ 바로 아래 사용자 추가 항목
|
|
bundled_target_domain: [modules, plugins, templates, lang-packs] # 보존 대상 4종
|
|
# 기본=코어 변경분만 적용(3-way) + --prune (공개 #64)
|
|
apply_mode: [incremental, prune, fallback_no_backup] # 파일 적용 방식
|
|
three_way_verdict: [unchanged_same, added_new, changed_size, changed_md5] # base↔theirs 판정
|
|
user_local_modification: [modified, untouched] # mine 이 base 와 다른지 (사용자 수정)
|
|
# targets 의 {domain}/_bundled 가 상위 protected({domain}) 를 오버라이드해 apply 되는지 (공개 #64 / 내부 #452)
|
|
bundled_apply_override: [override_applies, sibling_active_preserved, target_equals_protected_still_blocked]
|
|
protected_override_domain: [modules, plugins, templates, lang-packs] # 오버라이드 대상 4종 확장
|
|
|
|
exclusions:
|
|
- { source_structure: empty_top_level, active_subdir_state: present, reason: "source 가 비었으면 자동 발견 폴백 자체가 트리거 안 됨 — 활성 서브디렉토리 보존 무조건 보장" }
|
|
- { applied_targets_set: no_targets, source_structure: bundled_only, reason: "targets 가 비면 source 의 templates/ 자체가 자동 발견 대상이 됨 — protected_paths 분기로 차단되어야 정상" }
|
|
- { domain: language_packs, deactivated_columns: absent, source_url_column: column_absent, reason: "마이그레이션 미적용 환경 — 단발성 마킹/안내 모두 graceful skip 외 다른 효과 없음" }
|
|
- { apply_mode: prune, three_way_verdict: unchanged_same, reason: "prune 은 3-way 판정을 건너뛰고 전체 덮어쓰기 — three_way_verdict 무의미" }
|
|
- { apply_mode: fallback_no_backup, three_way_verdict: unchanged_same, reason: "백업 부재 시 base 자체가 없어 3-way 불가 — 전체 덮어쓰기로 회귀" }
|
|
- { apply_mode: incremental, user_local_modification: modified, three_way_verdict: changed_size, reason: "코어+사용자 동시 변경 — 코어 버전으로 갱신(§6 한계), 백업에서 복구 가능" }
|
|
- { apply_mode: prune, bundled_apply_override: override_applies, reason: "prune 은 3-way/protected 필터를 우회하고 전체 덮어쓰기 — 오버라이드 판정 자체가 무의미" }
|
|
- { bundled_apply_override: target_equals_protected_still_blocked, protected_override_domain: modules, reason: "target==protected 방어는 storage 같은 동일 경로 케이스로 검증 — 도메인 무관 (protected_override_domain 축과 직교 아님)" }
|
|
|
|
effects:
|
|
- applyDiscoveredTopLevelPaths_does_not_remove_active_subdirs_when_source_only_has_bundled
|
|
- applyDiscoveredTopLevelPaths_skips_parent_when_isCoveredByApplied_detects_child_in_applied
|
|
- applyDiscoveredTopLevelPaths_skips_parent_when_protected_paths_lists_parent
|
|
- applyDiscoveredTopLevelPaths_preserves_modules_plugins_templates_lang_packs_all_four_domains
|
|
- applyUpdate_targets_branch_continues_to_remove_orphans_within_bundled_subtree # _bundled 내부 stale 파일 정리는 정상 동작
|
|
- recoverActiveExtensionDirs_restores_from_bundled_when_active_missing
|
|
- recoverActiveExtensionDirs_skips_when_active_present_and_nonempty
|
|
- recoverActiveExtensionDirs_does_not_overwrite_user_modified_active_content
|
|
- recoverActiveExtensionDirs_marks_external_extension_inactive_with_reason
|
|
- recoverActiveExtensionDirs_skips_db_update_when_deactivated_columns_absent
|
|
- recoverActiveExtensionDirs_emits_stdout_with_reinstall_command_when_source_url_present
|
|
- recoverActiveExtensionDirs_emits_stdout_without_command_when_source_url_null
|
|
- recoverActiveExtensionDirs_does_not_write_preserve_ownership_marker_to_restored_dir
|
|
- recoverPendingStubFiles_creates_gitignore_and_gitkeep_when_absent
|
|
- recoverPendingStubFiles_preserves_existing_stubs_idempotently
|
|
- recoverPendingStubFiles_creates_pending_dir_when_missing
|
|
- verifyBundledLangPacksFallback_skips_when_active_bundled_present
|
|
- verifyBundledLangPacksFallback_recovers_from_deep_pending_source_unbounded_depth
|
|
# 사용자가 _bundled/ 바로 아래 직접 만든 항목 보존 (확장 3종 + 언어팩).
|
|
# effect 명 = 실제 테스트 메서드명 (CoreUpdateServiceTest / FilePermissionHelperTest).
|
|
- test_apply_update_preserves_user_added_bundled_subdir_all_domains
|
|
- test_apply_update_prune_still_cleans_stale_file_inside_bundled_extension # prune 모드에서만 stale 정리
|
|
- test_preserve_top_level_orphans_keeps_top_level_dir_and_file
|
|
- test_preserve_top_level_orphans_still_cleans_nested_orphans
|
|
- test_preserve_top_level_orphans_defaults_to_existing_behavior
|
|
# 기본=코어 변경분만 적용(3-way) + --prune (공개 #64).
|
|
# effect 명 = 실제 테스트 메서드명 (CoreBackupHelperApplyListTest / CoreUpdateServiceTest).
|
|
- test_unchanged_file_is_excluded_from_apply_list # base==theirs → 제외
|
|
- test_new_file_is_added_to_apply_list # base 없음 → added
|
|
- test_size_differing_file_is_changed # size 다름 → changed
|
|
- test_same_size_different_content_file_is_changed # md5 다름 → changed
|
|
- test_apply_update_incremental_preserves_user_modified_unchanged_file # 이슈 #64 회귀 가드 (.htaccess)
|
|
- test_apply_update_incremental_applies_only_listed_files # 목록만 적용
|
|
- test_apply_update_incremental_does_not_remove_orphans # 증분=삭제 안 함
|
|
- test_apply_update_prune_removes_orphans_and_overwrites_all # prune=전체 덮어쓰기+삭제
|
|
- test_apply_update_null_apply_list_falls_back_to_full_overwrite # 백업 부재 fallback
|
|
# targets 의 {domain}/_bundled 가 상위 protected 를 오버라이드해 코어 업데이트로 번들 확장 파일이 갱신됨 (공개 #64 / 내부 #452).
|
|
# effect 명 = 실제 테스트 메서드명 (CoreBackupHelperApplyListTest / CoreUpdateServiceTest).
|
|
- test_bundled_target_overrides_parent_protected_path # 4종 확장 전수 (dataProvider)
|
|
- test_target_equal_to_protected_is_still_excluded # target==protected 는 여전히 제외 (방어 유지)
|
|
- test_incremental_apply_reflects_changed_bundled_extension_file # computeApplyList→applyUpdate end-to-end 반영
|
|
|
|
test_files:
|
|
- tests/Unit/Services/CoreUpdateServiceTest.php
|
|
- tests/Unit/Helpers/FilePermissionHelperTest.php
|
|
- tests/Unit/Extension/CoreBackupHelperApplyListTest.php
|
|
- tests/Feature/Upgrades/Upgrade_7_0_0_beta_5RecoveryTest.php
|
|
|
|
# 본 시나리오 매니페스트의 axes cross product 는 50+ 케이스이나, 실제 핵심 회귀 가드는
|
|
# test_files 의 통과 테스트들이 SSoT — 매니페스트는 매트릭스 SSoT 역할.
|