위임 관리자(부관리자)가 권한·역할·표현식·비밀 콘텐츠 경계를 우회하던 결함군을 계층 대칭성 원칙으로 전건 차단한다. 약한 경로가 정상 응답을 내보내는 것이 유일한 증상이라, 게이트를 생산 지점 한 곳(SSoT)에 두고 같은 데이터를 내보내는 소비 경로 전부가 그 게이트를 경유하도록 맞췄다. - 등급 상한(rank ceiling): 슈퍼관리자 보호·역할/사용자 역할 배정(추가·제거 대칭)·일괄 상태변경·순서변경을 상세 경로와 동일 강도로 재적용. 가드는 DB 쓰기에 선행하여 거부 시 상태 불변. - 레이아웃 표현식: new Function/with 실행을 AST 화이트리스트 평가기로 교체. 비-문자열 computed 키 정규화(normalizeKey)·Object facade(리플렉션 static 제거)·legacy 접근자 차단. 저장측 검증·정적 검사와 3계층 동형. - secret 게이트: 비밀글의 댓글·첨부·문의 독립 경로 재적용, hash 파일서빙 소유권·비밀·발행 상태 검사 통일. - 신뢰 스크립트 호스트: 확장 선언 기반 + same-origin 브라우저 정규화를 런타임·저장측·정적검사 3층 동형화. - 회귀 감지: 단위·Feature·E2E·시나리오 매니페스트 전축 + audit 룰 4종 신설.
191 lines
14 KiB
YAML
191 lines
14 KiB
YAML
# audit:allow test-scenario-coverage reason: 본 매니페스트는 7.0.0-beta.5 도입 "버전별 데이터 스냅샷" 인프라의 시나리오 매트릭스 SSoT. 핵심 회귀 가드는 test_files 의 통과 테스트로 커버.
|
||
|
||
feature: 버전별 데이터 스냅샷 (AbstractUpgradeStep + data/{version}/ 격리)
|
||
|
||
description: |
|
||
spawn 자식 (경로 B) 의 fresh-load 가 멀티 버전 점프 시 각 step 의 시더·Manager·헬퍼를
|
||
모두 *최종 버전 메모리* 로 동작하게 만드는 비대칭 회귀를 차단한다.
|
||
|
||
본 인프라는:
|
||
1. AbstractUpgradeStep — 카탈로그/변환/핫픽스 위임 규약
|
||
2. upgrades/data/{version}/ — manifest + delta JSON + appliers/ + migrations/ 격리
|
||
3. 버전 namespace (App\Upgrades\Data\V{ver}\Appliers|Migrations) — 다른 버전 클래스명 충돌 회피
|
||
4. CoreUpdateService::runUpgradeSteps 의 fatal 가드 — beta.5+ 미상속 step 즉시 throw
|
||
5. audit rule upgrade-step-data-snapshot (severity: error) — PR 시점 차단
|
||
6. dogfood — Upgrade_7_0_0_beta_5 의 모든 핫픽스 5종 + IDV 권한 rename 을 새 구조로 보존
|
||
|
||
axes:
|
||
extension_type: [core, bundled_module, bundled_plugin, external_module, external_plugin, bundled_template_na, bundled_langpack_na] # 적용 범위 (NA = upgrade step 시스템 부재)
|
||
extension_g7_version_constraint: [ge_beta_5, ge_beta_4_legacy, null_constraint, malformed] # ExtensionUpgradeGuardHelper since-version 분기
|
||
step_version: [pre_beta_5_legacy, beta_5, post_beta_5, ext_below_since, ext_at_since, ext_above_since] # 버전 가드 트리거 조건 (코어 + 확장 working-version 기준 양쪽)
|
||
step_class_inheritance: [extends_abstract, implements_only, no_implements, callable_closure]
|
||
data_dir_state: [absent, present_manifest_only, present_full]
|
||
manifest_kind: [permissions, role_permissions, notification_definitions, identity_policies, unknown]
|
||
applier_namespace: [matching_version, mismatched_version, missing_class]
|
||
migration_filename_prefix: [no_prefix, two_digit_prefix, three_digit_prefix, alphabetic_only]
|
||
migration_order: [01_02_03_alphabetical, 03_01_02_explicit_via_prefix]
|
||
delta_operation: [added_only, removed_only, renamed_only, mixed, empty_delta]
|
||
rename_collision: [new_row_absent, new_row_exists, both_absent] # IDV 권한 마이그레이션 분기
|
||
pivot_state: [no_grant, single_role, dual_grant_on_both, multi_role]
|
||
v_one_violation_in_applier: [seeders_use, app_service_call, app_manager_call, app_repository_call, none]
|
||
v_one_violation_in_migration: [seeders_use, app_service_call, app_manager_call, app_repository_call, none]
|
||
cross_version_reference: [self_only, other_version_in_applier, other_version_in_migration]
|
||
audit_allow_inline: [present, absent] # 면제 주석 적용 여부
|
||
hotfix_filesystem_state: [healthy, broken_active_dir, broken_pending_stub, broken_langpacks_bundled, broken_storage_symlink]
|
||
cross_extension_namespace_collision: [single, two_extensions_same_step_version_class] # known limitation 검증
|
||
|
||
exclusions:
|
||
- { step_version: pre_beta_5_legacy, step_class_inheritance: extends_abstract, reason: "legacy step 은 AbstractUpgradeStep 도입 전 코드 — 상속 불가" }
|
||
- { step_version: pre_beta_5_legacy, data_dir_state: present_full, reason: "legacy step 은 data/ 디렉토리 미사용" }
|
||
- { applier_namespace: missing_class, manifest_kind: unknown, reason: "kind 자체가 모르는 값이면 applier 클래스 매핑 단계 진입 안 함" }
|
||
- { manifest_kind: unknown, delta_operation: mixed, reason: "unknown kind 는 manifest 단계에서 차단 — delta 미접근" }
|
||
- { rename_collision: both_absent, pivot_state: dual_grant_on_both, reason: "양쪽 row 부재 시 피벗 상태와 무관 (silent skip)" }
|
||
- { cross_version_reference: other_version_in_applier, v_one_violation_in_applier: app_service_call, reason: "두 위반 동시 케이스는 단일 axis 분석 의미 흐림" }
|
||
- { extension_type: bundled_template_na, reason: "템플릿은 upgrade step 시스템 부재 — 본 가드 대상 외 (미래 도입 시 자동 상속)" }
|
||
- { extension_type: bundled_langpack_na, reason: "언어팩은 upgrade step 시스템 부재 — 본 가드 대상 외" }
|
||
- { extension_type: external_module, extension_g7_version_constraint: ge_beta_5, audit_allow_inline: absent, reason: "외부 확장은 활성 디렉토리 — audit 미적용 (런타임 가드만 발화)" }
|
||
- { extension_type: external_plugin, extension_g7_version_constraint: ge_beta_5, audit_allow_inline: absent, reason: "외부 확장은 활성 디렉토리 — audit 미적용 (런타임 가드만 발화)" }
|
||
- { extension_g7_version_constraint: ge_beta_4_legacy, step_class_inheritance: no_implements, reason: "legacy g7_version 확장은 가드 미발동 (호환 유지)" }
|
||
- { extension_g7_version_constraint: null_constraint, step_class_inheritance: no_implements, reason: "g7_version 미선언 = legacy 로 판정 (호환)" }
|
||
- { extension_g7_version_constraint: malformed, step_class_inheritance: no_implements, reason: "파싱 실패 시 보수적으로 legacy 처리 (가드 미발동)" }
|
||
# cross_extension_namespace_collision 의 두 케이스 모두 회귀 가드 보유:
|
||
# - single: 기본 케이스 (다른 step 버전이면 자명 격리)
|
||
# - two_extensions_same_step_version_class: namespace 에 확장 식별자 segment 가
|
||
# 포함되어 PHP compile-time fatal 회피. 정적 검증(DataSnapshotTest) + 실행 검증
|
||
# (ExtensionAbstractUpgradeStepFullFlowTest::test_two_extensions_..._isolated_by_namespace)
|
||
|
||
effects:
|
||
# DataSnapshot — 스냅샷 로드/네임스페이스 격리
|
||
- empty_snapshot_when_manifest_absent
|
||
- versioned_namespace_converts_dots_and_hyphens_to_underscore
|
||
- versioned_namespace_branches_by_core_bundled_module_bundled_plugin_external
|
||
- same_step_version_different_extensions_get_distinct_namespaces
|
||
- manifest_kind_resolves_to_applier_class_and_instantiates
|
||
- applier_file_missing_or_namespace_mismatch_throws_runtime_exception
|
||
# AbstractUpgradeStep — 위임 규약
|
||
- run_delegates_in_order_data_snapshot_then_migrations_then_post_run
|
||
- migrations_execute_in_filename_sort_order
|
||
- two_digit_prefix_stripped_before_class_name_mapping
|
||
- data_dir_absent_silently_skips_without_fatal
|
||
# 코어 upgrade step 가드
|
||
- core_beta_5_or_later_step_without_abstract_inheritance_throws
|
||
- core_beta_5_or_later_step_with_inheritance_runs
|
||
- core_legacy_beta_4_or_earlier_step_passes_without_inheritance
|
||
# 확장 upgrade step 가드
|
||
- bundled_module_ge_beta_5_without_inheritance_throws
|
||
- bundled_module_with_inheritance_runs
|
||
- bundled_module_legacy_g7_version_bypasses_guard
|
||
- bundled_module_callable_closure_bypasses_guard
|
||
- step_version_below_extension_since_version_bypasses_guard
|
||
- bundled_plugin_mirrors_module_guard_branches
|
||
# 확장 전체 흐름
|
||
- extension_full_flow_preserves_applier_migration_post_run_order
|
||
- data_dir_resolves_to_extension_directory_via_reflection
|
||
- manifest_json_absent_is_graceful_silent
|
||
- manifest_applier_file_missing_throws_explicit_runtime_exception
|
||
- two_modules_same_step_version_same_migration_class_both_run_own_code
|
||
# IDV 권한 rename 마이그레이션
|
||
- permissions_applier_renames_in_place_when_new_row_absent
|
||
- identity_permission_pivot_merges_on_collision_path
|
||
- old_pivot_removed_and_new_pivot_preserved_for_dual_granted_role
|
||
- permission_rename_is_idempotent_across_repeated_runs
|
||
# beta.5 복구 핫픽스
|
||
- four_domain_active_directories_recovered
|
||
- external_extension_loss_triggers_one_shot_deactivation_with_notice
|
||
- pending_stub_regenerated
|
||
- lang_packs_bundled_fallback_applied
|
||
- public_storage_symlink_restored
|
||
# since-version 판정 헬퍼
|
||
- resolve_since_version_branches_null_empty_legacy_beta5_or_and_parse_failure
|
||
- assert_abstract_inheritance_branches_by_step_version_vs_since_version
|
||
- guard_exception_message_includes_extension_type_identifier_step_version_and_core_version
|
||
|
||
test_files:
|
||
- tests/Unit/Extension/Upgrade/DataSnapshotTest.php
|
||
- tests/Unit/Extension/AbstractUpgradeStepTest.php
|
||
- tests/Unit/Extension/Helpers/ExtensionUpgradeGuardHelperTest.php
|
||
- tests/Feature/Upgrades/RunUpgradeStepsAbstractGuardTest.php
|
||
- tests/Feature/Upgrades/Upgrade_7_0_0_beta_5PermissionRenameTest.php
|
||
- tests/Feature/Upgrades/Upgrade_7_0_0_beta_5RecoveryTest.php
|
||
- tests/Feature/Upgrades/ExtensionAbstractGuardTest.php
|
||
- tests/Feature/Upgrades/ExtensionAbstractUpgradeStepFullFlowTest.php
|
||
|
||
# 파일별 커버 범위 상세 (test_files 의 각 항목이 무엇을 보장하는지)
|
||
test_coverage_detail:
|
||
unit:
|
||
- path: tests/Unit/Extension/Upgrade/DataSnapshotTest.php
|
||
covers:
|
||
- empty / fromManifest 미존재 시 빈 스냅샷
|
||
- versionedNamespace 의 점/하이픈 → underscore 변환
|
||
- versionedNamespace 의 코어/번들 모듈/번들 플러그인/외부 확장 경로 분기
|
||
- 같은 step 버전이라도 다른 확장은 서로 다른 namespace 부여 (격리 회귀 가드)
|
||
- manifest → Applier 동적 로드 + 클래스 인스턴스화
|
||
- Applier 파일 부재 / namespace 불일치 시 RuntimeException
|
||
- path: tests/Unit/Extension/AbstractUpgradeStepTest.php
|
||
covers:
|
||
- run() 의 위임 흐름 (dataSnapshot → dataMigrations → postRun)
|
||
- migrations 파일명 정렬 순서 실행
|
||
- 두 자리 prefix 자동 제거 후 클래스명 매핑
|
||
- data/{version}/ 디렉토리 부재 시 silent skip
|
||
feature:
|
||
- path: tests/Feature/Upgrades/RunUpgradeStepsAbstractGuardTest.php
|
||
covers:
|
||
- beta.5+ AbstractUpgradeStep 미상속 시 CoreUpdateOperationException
|
||
- beta.5+ 상속 시 정상 실행
|
||
- legacy beta.4 이하는 미상속이어도 통과
|
||
- path: tests/Feature/Upgrades/Upgrade_7_0_0_beta_5PermissionRenameTest.php
|
||
covers:
|
||
- PermissionsApplier 의 in-place rename (신 row 부재 경로)
|
||
- IdentityPermissionPivotMerge 의 충돌 경로 피벗 병합
|
||
- 양쪽 부여된 role 의 옛 피벗 삭제 + 신 피벗 보존
|
||
- 멱등성 (두 번 실행 안전)
|
||
- path: tests/Feature/Upgrades/Upgrade_7_0_0_beta_5RecoveryTest.php
|
||
covers:
|
||
- 4개 도메인 활성 디렉토리 복구 (modules / plugins / templates / language_packs)
|
||
- 외부 확장 손실 시 단발성 비활성화 + 안내
|
||
- _pending stub 재생성
|
||
- lang-packs/_bundled fallback
|
||
- public/storage symlink 복구
|
||
- path: tests/Feature/Upgrades/ExtensionAbstractGuardTest.php
|
||
covers:
|
||
- 번들 모듈 — g7_version >= beta.5, 미상속 step → RuntimeException
|
||
- 번들 모듈 — 상속 step → 정상 실행
|
||
- 번들 모듈 — legacy g7_version → 가드 우회
|
||
- 번들 모듈 — callable closure → 가드 우회 (호환)
|
||
- 번들 모듈 — step 버전 < 확장 since-version → 가드 우회 (legacy step 호환)
|
||
- 번들 플러그인 — 미상속/상속/legacy/null g7_version 4종 동등 미러링
|
||
- path: tests/Feature/Upgrades/ExtensionAbstractUpgradeStepFullFlowTest.php
|
||
covers:
|
||
- 모듈 측 전체 흐름 — Applier → Migration → postRun 순서 보존
|
||
- 플러그인 측 전체 흐름 (동일)
|
||
- dataDir() 가 확장 디렉토리로 자동 분기 (ReflectionClass 기반)
|
||
- manifest.json 부재 시 graceful silent (no fatal)
|
||
- manifest 의 Applier 파일 미존재 시 명확한 RuntimeException
|
||
- 두 다른 모듈이 같은 step 버전 + 같은 Migration 클래스명 → namespace 격리로 양쪽 모두 자기 코드 실행
|
||
- DataSnapshot::versionedNamespace 가 코어/확장간 서로 다른 namespace 부여
|
||
unit_helpers:
|
||
- path: tests/Unit/Extension/Helpers/ExtensionUpgradeGuardHelperTest.php
|
||
covers:
|
||
- resolveSinceVersion 분기 (null / 빈 / legacy / beta.5+ / OR / 파싱 실패)
|
||
- assertAbstractInheritance 분기 (stepVersion < / = / > sinceVersion × 상속/미상속)
|
||
- 예외 메시지 포맷 (확장 타입 / 식별자 / step 버전 / 도입 코어 버전 포함)
|
||
|
||
manual_verification:
|
||
- "실제 beta.4 환경에서 IDV 권한 옛 키 시드 후 php artisan core:update 로 beta.5 점프 → permissions / role_permissions 의 신 키 식별자 + 피벗 보존 확인"
|
||
- "fresh-load invariant 확인: data/7.0.0-beta.5/ 안의 클래스에서 Database\\Seeders 또는 app(*Service|*Manager|*Repository::class) 호출 시 audit error 발화"
|
||
- "공용 디렉토리 app/Extension/Upgrade/Appliers/ 또는 app/Extension/Upgrade/Migrations/ 생성 시 audit error 발화"
|
||
- "더미 _bundled 모듈에 g7_version=>=7.0.0-beta.5 + AbstractUpgradeStep 미상속 step 작성 → php artisan module:update 실행 시 RuntimeException 확인 (메시지에 must extend App\\Extension\\AbstractUpgradeStep 포함)"
|
||
- "동일 모듈에 step 을 extends AbstractUpgradeStep 으로 변경 + upgrades/data/{version}/manifest.json 추가 → 재실행 시 정상 통과"
|
||
- "g7_version=>=7.0.0-beta.4 인 더미 모듈에 미상속 step 작성 → 가드 우회 확인 (legacy 호환)"
|
||
- "동일 시나리오를 플러그인으로 반복"
|
||
|
||
upstream_dependencies:
|
||
- "CoreUpdateService::runUpgradeSteps (line 1593+ fatal 가드 — 코어)"
|
||
- "ModuleManager::runUpgradeSteps (line 4046+ fatal 가드 — 번들 모듈)"
|
||
- "PluginManager::runUpgradeSteps (line 4228+ fatal 가드 — 번들 플러그인)"
|
||
- "ExtensionUpgradeGuardHelper (확장 since-version 판정 SSoT)"
|
||
- "AbstractUpgradeStep::run (final 메서드, 위임 순서 강제 — 코어/확장 공용)"
|
||
- "정적 audit 룰 (PR 시점 차단 — 코어/번들 확장 동시)"
|
||
- "docs/extension/upgrade-step-guide.md §13 (규약 문서)"
|
||
- "docs/extension/module-basics.md / plugin-development.md (확장 작성자 진입점)"
|