chore(core,board,ecommerce,page,gdpr): 7.0.6 공개 릴리즈 누출 검사 차단 요인 제거

prepare-release.sh 의 파일 본문 누출 검사가 error 10건으로 릴리즈를 차단하고 있었다.
공개 저장소는 를 제외하므로, 배포되는 파일이 그 경로나 를 참조하면
공개본에서 깨진 참조가 된다.

- 시나리오 매니페스트 4건: test_files 의 내부 하네스 테스트 경로 제거
- Playwright spec 3건: 룰 파일 경로/@see 포인터를 자기완결 서술로 교체
 (@see 2건은 이미 stale — 가리키던 사례 번호가 실제와 달랐다)
- 소스/테스트 주석 3건: 파일명 노출 제거

내부 역할 호칭이 공개본에 영구 노출되는 warn 23건 중 21건도 함께 중립 표현으로
정정했다. API 표면 파일 2건은 주석만 고쳐도 문서 커버리지 게이트에 영구 면제가
필요해져 보류한다 — warn 은 릴리즈를 차단하지 않는다.

부수로 드러난 기존 결함 둘을 같은 세션에서 처리:
- gdpr Playwright config 에 locale 고정 누락 — 첫 로드가 en-US 로 나가 화면이 영어로
 굳으면 한국어 문구를 단언하는 spec 이 전부 실패한다
- 언어팩 시나리오의 정적 검사 계층 전용 효과 2건을 effects 에서 rules_layer_coverage
 로 이관 — 런타임 테스트로 성립하지 않는 항목이라 그 칸이 정합 위치다

검증: 누출 검사 error 0, audit --working 위반 0(룰 192), 시나리오 매니페스트 8건
errors 0, gdpr Vitest 148 green, PHP lint/JSON/YAML 정상.
버전 정합은 코어 working 7.0.6 / 공개 latest 7.0.5 = new-bump 로 릴리즈 가능 상태.
This commit is contained in:
HeuJung
2026-08-10 17:46:17 +09:00
parent 2ba4a760fb
commit 3f29f9fd1e
26 changed files with 46 additions and 38 deletions
@@ -12,7 +12,7 @@
* 단위/렌더링:
* - templates/_bundled/sirsoft-basic/src/__tests__/layouts/board-post-navigation-list-return.test.tsx
* 가 실 `_navigation.json` 의 액션 params 로 mergeQuery 왕복을 고정
* - .claude/scripts/audit/rules/layout-list-context-navigate-merge-query.cjs 가 전 저장소 재발 차단
* - 저장소 전역 정적 검사가 목록 클러스터 내 navigate 의 mergeQuery 누락을 재발 차단
* 이 spec 은 브라우저 수준(실제 URL + 목록 API 요청 파라미터)을 담당한다.
*
* @scenario board-list-context-round-trip
@@ -35,7 +35,6 @@ test_files:
- modules/_bundled/sirsoft-ecommerce/resources/js/__tests__/layouts/admin-list-context-round-trip.test.ts
- modules/_bundled/sirsoft-page/resources/js/__tests__/layouts/admin-list-context-round-trip.test.ts
- templates/_bundled/sirsoft-admin_basic/src/__tests__/layouts/admin-list-context-round-trip.test.ts
- .claude/scripts/audit/__tests__/layout-list-context-navigate-merge-query.test.cjs
validation:
note: |
@@ -178,7 +178,7 @@ class CouponIssueRepository implements CouponIssueRepositoryInterface
// coupon 관계를 직렬화하지 않지만, 이 목록은 훅
// `sirsoft-ecommerce.user_coupon.filter_list_result` 로 확장에 그대로 전달된다.
// 제3자 확장이 쿠폰 원본 정의(쿠폰명·할인율·사용조건)를 읽고 있을 수 있어 현행 유지
// (PO 결정 2026-08-04). 번들 템플릿에는 아직 대응 마이페이지 화면이 없다.
// (프로젝트 결정 2026-08-04). 번들 템플릿에는 아직 대응 마이페이지 화면이 없다.
relations: ['coupon'],
resultCap: PaginationLimits::resultCap('ecommerce.coupon_issues'),
);
@@ -705,7 +705,7 @@ class CashReceiptService
/**
* 프로바이더 원응답에서 민감 키를 마스킹합니다.
*
* PO 결정: 전체 응답을 보관하되 알려진 민감 키를 재귀적으로 가린다.
* 프로젝트 결정: 전체 응답을 보관하되 알려진 민감 키를 재귀적으로 가린다.
*
* @param mixed $response 원응답
* @return array<string, mixed>|null 마스킹된 응답 (배열이 아니면 null)
@@ -28,7 +28,7 @@ namespace Modules\Sirsoft\Ecommerce\Support;
* 를 이 계약이 도입된 `>=1.1.0` 이상으로 선언해야 한다.
*
* 시그니처를 바꾸거나 제거할 때는 위 소비 확장을 전수 확인하고 각 확장의 최소 버전
* 제약을 함께 올린다 (CLAUDE.md "버전 동기화 의무" ②).
* 제약(`dependencies.modules.sirsoft-ecommerce`)을 함께 올린다.
*
* @since 1.1.0
*/
@@ -2,7 +2,7 @@
* 이커머스 모듈 Playwright E2E 설정.
*
* 코어 `playwright.config.ts` 와 동일한 base URL 해석 우선순위를 따른다 — 모듈도 활성 호스트가
* 가변(개발자/CI/PO 환경별로 다른 도메인)이므로 하드코딩 회피.
* 가변(개발자/CI/운영 환경별로 다른 도메인)이므로 하드코딩 회피.
*
* Base URL 해석:
* 1. PLAYWRIGHT_BASE_URL 환경변수 (CI/명시적 오버라이드)
@@ -28,7 +28,8 @@
* 부작용: ②③ 은 실제 주문을 생성한다(결제 대기 상태). 무통장입금은 외부 PG 를 거치지 않으므로
* 샌드박스 의존이 없고, 생성된 주문은 설정된 기한이 지나면 미입금 자동취소 대상이 된다.
*
* @see .claude/docs/frontend/troubleshooting-backend.md 사례 21
* 회귀 배경: 입금기한 일수 설정이 문자열로 영속되면 기한 산정이 Carbon 의 strict 타입 경계에서
* TypeError 를 던져 해당 주문 요청이 전부 500 이 되던 결함이 있었다.
*/
import { test, expect, authenticatePage } from '../../fixtures/ecommerce-auth';
import type { Page } from '@playwright/test';
@@ -39,7 +39,7 @@ notes: |
(`OrderCalculationService`)가 그 관계를 직접 읽고, 미로드 시 항목마다 재조회하므로 좁히면
페이로드 대신 쿼리가 늘어난다. 게다가 계산기가 `is_active` 를 거르지 않아 로드 경로에 따라
배송비가 달라질 수 있다 — 계산 경로를 함께 손대야 안전한 축이라 본 이슈에서 다루지 않는다
(PO 결정, 2026-08-05).
(프로젝트 결정, 2026-08-05).
E2E 미해당: 응답 형태가 불변이라 브라우저 경로에 새 분기가 없다. 소비 사실 고정은
레이아웃 원문 검사(`cartWishlistImageConsumers.test.ts`)가 담당한다.
@@ -11,7 +11,7 @@ description: |
리터럴 객체다. 슬롯에 주입된 모듈 폼이 _local 에 값을 써도 그 body 에 실릴 방법이 없다 —
결제 버튼에 id 가 없어 overlay 로 타겟할 수 없고, inject_props 는 컴포넌트 props 전용이라
actions[].params.body 에 도달하지 못한다. 그대로 두면 구매자가 신청해도 주문에 아무 기록이 남지 않는다.
→ PO 결정: body 를 통짜 표현식으로 바꾸고 확장 병합 칸(...(_local.checkoutExtraPayload ?? {})) 한 개를 연다.
→ 프로젝트 결정: body 를 통짜 표현식으로 바꾸고 확장 병합 칸(...(_local.checkoutExtraPayload ?? {})) 한 개를 연다.
템플릿은 현금영수증을 알지 않고, 모듈은 그 칸에만 쓴다. 확장 미주입 시 payload 는 전환 전과 동일하다.
발견한 사전 결함(본 세션에서 수정):
@@ -29,7 +29,7 @@ description: |
· 발급 버튼과 재발급 버튼의 노출 조건이 배타적이지 않아 FAILED 상태에서 둘 다 떴다.
· 이력 표가 원시값을 노출했다 — 취소 행에 "발급 완료", 실패 행 일시 "-"(issued_at null), issue/FAILED.
환불계좌는 PG·현금영수증 프로바이더와 무관한 순수 주문 정보이므로 템플릿이 직접 소유한다(PO 결정).
환불계좌는 PG·현금영수증 프로바이더와 무관한 순수 주문 정보이므로 템플릿이 직접 소유한다(프로젝트 결정).
취소 시점 갱신은 레이아웃이 아니라 cancelOrderHandlers.ts 가 수행한다 — 취소 POST body 를
그 핸들러가 조립하기 때문이다.
@@ -215,7 +215,7 @@ effects:
- portable_override_applies_below_1024_on_render # 실렌더: 세로 스택 전환 (F5)
- portable_override_absent_at_1024_on_render # 실렌더: 1024px 부터 base 복귀
# 데이터 위생 (감사 지적 C8, PO 결정 2026-07-10)
# 데이터 위생 (감사 지적 C8, 프로젝트 결정 2026-07-10)
- cash_receipt_request_not_persisted_for_non_dbank # 발급 불가 주문에 암호문 미보관
# 자진발급 설정 안내 (감사 지적 W-5)
@@ -22,7 +22,6 @@ effects:
test_files:
- modules/_bundled/sirsoft-ecommerce/tests/Playwright/specs/admin/order-admin-list-context-round-trip.spec.ts
- modules/_bundled/sirsoft-ecommerce/resources/js/__tests__/layouts/admin-list-context-round-trip.test.ts
- .claude/scripts/audit/__tests__/layout-list-context-navigate-merge-query.test.cjs
validation:
note: |
@@ -3,7 +3,7 @@ feature: 리뷰 목록의 첨부 이미지 계약 고정 (#518 / 공개 #76)
description: |
#518 원안은 리뷰 목록에서 `images` 를 빼고 대표 1장 + `image_count` 로 줄이려 했다.
소비처를 재실측한 결과 그 판단이 빗나갔다 — 관리자·공개 두 목록 모두 첨부 이미지를
실제로 그린다. 착수하지 않는다 (PO 결정, 2026-08-05).
실제로 그린다. 착수하지 않는다 (프로젝트 결정, 2026-08-05).
이 매니페스트는 "줄였다" 를 기록하는 것이 아니라 **줄일 수 없다는 사실을 고정**한다.
제거 시 조용히 깨지기 때문이다: 썸네일이 `(review.images ?? []).length > 0` 가드 뒤에
@@ -2,7 +2,7 @@
* 페이지 모듈 Playwright E2E 설정.
*
* 코어 `playwright.config.ts` 및 이커머스 모듈 설정과 동일한 base URL 해석 우선순위를 따른다 —
* 모듈도 활성 호스트가 가변(개발자/CI/PO 환경별로 다른 도메인)이므로 하드코딩 회피.
* 모듈도 활성 호스트가 가변(개발자/CI/운영 환경별로 다른 도메인)이므로 하드코딩 회피.
*
* Base URL 해석:
* 1. PLAYWRIGHT_BASE_URL 환경변수 (CI/명시적 오버라이드)
@@ -19,7 +19,6 @@ effects:
test_files:
- modules/_bundled/sirsoft-page/tests/Playwright/specs/admin/page-admin-list-context-round-trip.spec.ts
- modules/_bundled/sirsoft-page/resources/js/__tests__/layouts/admin-list-context-round-trip.test.ts
- .claude/scripts/audit/__tests__/layout-list-context-navigate-merge-query.test.cjs
validation:
note: |
@@ -534,7 +534,7 @@
]
},
{
"comment": "항목 리스트 (이슈 #430 재설계) — 상태 배지 + 동의/철회 버튼 방식. 필수=상태 배지 '항상 적용' + 잠금 버튼(액션 없음), 선택=상태 배지(동의함/거부함/미설정) + 동의·철회 버튼(확인 모달 경유). 동의일 컬럼은 사용자 화면에서 제거(동의한 항목만 라벨 아래 날짜 표시, 감사 로그 전체는 관리자 화면에만 존재). 초기 설계였던 on/off 스위치는 user 템플릿 스타일 회귀 + PO 지시로 배지+버튼 방식으로 대체됨(2026-07-21).",
"comment": "항목 리스트 (이슈 #430 재설계) — 상태 배지 + 동의/철회 버튼 방식. 필수=상태 배지 '항상 적용' + 잠금 버튼(액션 없음), 선택=상태 배지(동의함/거부함/미설정) + 동의·철회 버튼(확인 모달 경유). 동의일 컬럼은 사용자 화면에서 제거(동의한 항목만 라벨 아래 날짜 표시, 감사 로그 전체는 관리자 화면에만 존재). 초기 설계였던 on/off 스위치는 user 템플릿 스타일 회귀 + 프로젝트 지시로 배지+버튼 방식으로 대체됨(2026-07-21).",
"type": "basic",
"name": "Div",
"if": "{{(gdprMeConsents?.data?.consents ?? []).length > 0}}",
@@ -564,7 +564,7 @@ describe('admin/plugin_settings.json — 3 카드 (operator / cookie_banner / au
/**
* sequence handler 구조 회귀 가드 — actions 배열이 params 안에 잘못 중첩되어
* (CLAUDE.md 절대 금지 패턴) 있었던 결함. 엔진(ActionDispatcher.handleSequence)이
* (레이아웃 JSON 금지 패턴) 있었던 결함. 엔진(ActionDispatcher.handleSequence)이
* 두 위치를 모두 인식하는 fallback을 갖고 있어 동작 자체는 정상이었지만, 정석
* 위치(actions가 액션 객체의 top-level)로 바로잡는다.
*/
@@ -312,7 +312,7 @@ describe('extensions/mypage_privacy_tab.json — 마이페이지 GDPR 동의 매
* @effects mypage_status_date_line_consented_only
*/
it('이슈 #430/#509 — 동의·철회 이력이 있는 항목만 날짜 줄 노출 (서버가 status_at_formatted 제공)', () => {
// PO 결정(2026-07-20) + 이슈 #509 16번 갱신: 날짜는 "동의한 항목" + "철회 이력이 있는 항목".
// 프로젝트 결정(2026-07-20) + 이슈 #509 16번 갱신: 날짜는 "동의한 항목" + "철회 이력이 있는 항목".
// 거부·신규 미설정은 서버가 status_at_formatted 를 null 로 내려주므로 날짜 줄이 렌더되지 않는다.
// 프론트는 status_label && status_at_formatted 가 둘 다 있을 때만 날짜 줄을 그리며, 값 자체는
// 서버가 상태별(동의일/철회일)로 계산해 내려주므로 프론트 조건식은 상태 무관 동일하게 유지된다.
@@ -328,7 +328,7 @@ describe('extensions/mypage_privacy_tab.json — 마이페이지 GDPR 동의 매
* @effects mypage_status_date_line_revoked_color_distinction
*/
it('이슈 #509 16번 — 동의·철회 날짜 줄을 서로 다른 색으로 균형있게 강조 (동의=초록/철회=빨강, 배지·관리자 이력 화면과 동일 톤)', () => {
// 철회만 강조되고 동의는 밋밋해 보인다는 PO 피드백 반영 — 동의도 철회와 대등하게 강조한다.
// 철회만 강조되고 동의는 밋밋해 보인다는 사용성 피드백 반영 — 동의도 철회와 대등하게 강조한다.
// status === 'revoked' 면 text-red-600/dark:text-red-400, 그 외(동의)는 text-green-600/dark:text-green-400.
const text = serializeForSearch(section);
expect(text).toContain("consent?.status === 'revoked'");
@@ -2,7 +2,7 @@
* GDPR 플러그인 Playwright E2E 설정.
*
* 코어 `playwright.config.ts` 와 동일한 base URL 해석 우선순위를 따른다 — 플러그인도 활성 호스트가
* 가변(개발자/CI/PO 환경별로 다른 도메인)이므로 하드코딩 회피.
* 가변(개발자/CI/운영 환경별로 다른 도메인)이므로 하드코딩 회피.
*
* Base URL 해석:
* 1. PLAYWRIGHT_BASE_URL 환경변수 (CI/명시적 오버라이드)
@@ -76,6 +76,12 @@ export default defineConfig({
],
use: {
baseURL: resolveBaseUrl(),
// spec 이 한국어 화면 문구를 단언하므로 로케일을 고정한다.
// 로케일 우선순위는 localStorage g7_locale → 서버 응답값 → 'ko' 이고, 서버값은 미인증
// 요청에서 Accept-Language 로 결정된다(SetLocale 미들웨어). Playwright 의 locale 옵션이
// 그 헤더를 만들므로, 지정하지 않으면 첫 페이지 로드가 en-US 로 나가 화면이 영어로 렌더되고
// 엔진이 그 값을 localStorage 에 저장해 이후 인증해도 세션 전체가 영어로 고정된다.
locale: 'ko-KR',
trace: 'retain-on-failure',
screenshot: 'only-on-failure',
ignoreHTTPSErrors: true,
@@ -1,6 +1,6 @@
/**
* E2E: 관리자 환경설정 > 쿠키 배너 > 정책 버전 > 보기 — 본문 페이지 링크 (#509 체크리스트 12번)
* + 관리자 환경설정 카드 제목 색상 회귀 가드 (PO 스크린샷 발견)
* + 관리자 환경설정 카드 제목 색상 회귀 가드 (브라우저 스크린샷으로 발견)
* + 정책 버전 이력 토글 sequence handler 회귀 가드
*
* @scenario admin_gdpr_policy_version_snapshot_open_policy_page_link
@@ -13,11 +13,11 @@
* 모달의 본문 페이지 링크 href 를 검증할 수 있다.
*
* 또한 카드 제목(H3)이 에러/경고 전용 시맨틱(text-error-strong, red 계열)을 오용해
* "운영 주체" 등 일반 카드 제목이 빨간색으로 표시되던 결함을 PO 가 스크린샷으로 발견해
* "운영 주체" 등 일반 카드 제목이 빨간색으로 표시되던 결함을 브라우저 스크린샷으로 발견해
* section-heading-md(중립 톤)로 수정 — 실제 렌더링된 색상을 브라우저에서 검증한다.
*
* 정책 버전 이력 토글 버튼의 sequence handler 가 actions 를 params 안에 잘못 중첩하고
* 있던 결함(CLAUDE.md 절대 금지 패턴)도 함께 발견 — 엔진이 두 위치 모두 인식하는
* 있던 결함(레이아웃 JSON 금지 패턴)도 함께 발견 — 엔진이 두 위치 모두 인식하는
* fallback을 갖고 있어 동작 자체는 정상이었으나 정석 위치(top-level)로 바로잡았다.
* 실제 클릭 시 이력 표가 펼쳐지는지(=sequence 의 setState 가 실행됐는지)를 검증한다.
*
@@ -6,7 +6,7 @@
*
* 배경: `_global.gdprBannerDismissedFor` 가 "닫힘 여부"(boolean)가 아니라 "누가 닫았는지"
* (사용자 식별자)를 저장하도록 수정되기 전에는, 게스트로 배너를 닫은 뒤 동의 이력이 없는
* 다른 계정으로 로그인해도 배너가 계속 숨겨져 있는 결함이 있었다 (PO 가 스크린샷으로 재현·지적).
* 다른 계정으로 로그인해도 배너가 계속 숨겨져 있는 결함이 있었다 (브라우저 스크린샷으로 재현·지적).
* 현재는 cookie_banner.json 의 노출 조건이 `_global.gdprBannerDismissedFor !== (사용자 식별자)`
* 로 비교하므로, 로그인으로 사용자가 바뀌면(게스트 uuid → 회원 uuid) 다시 노출되어야 한다.
*
@@ -66,7 +66,7 @@ class GdprUserConsentResourceTest extends PluginTestCase
public function test_rejected_optional_maps_to_rejected_status_with_no_date_label(): void
{
// PO 결정(2026-07-20) — 날짜/라벨은 "동의한 항목만" 노출. 거부는 상태만 rejected 로 내려주고
// 프로젝트 결정(2026-07-20) — 날짜/라벨은 "동의한 항목만" 노출. 거부는 상태만 rejected 로 내려주고
// status_label·status_at_formatted 는 null (프론트가 날짜 줄을 렌더하지 않음).
$consent = new GdprUserConsent([
'consent_key' => 'cookie_analytics',
@@ -701,13 +701,13 @@ class LanguagePackControllerTest extends TestCase
}
/**
* 번들 설치도 외부 소스와 동일하게 활성화 권한 게이트를 적용한다 (PO 결정, 2026-08-04).
* 번들 설치도 외부 소스와 동일하게 활성화 권한 게이트를 적용한다 (프로젝트 결정, 2026-08-04).
*
* 설치(install)와 활성화(manage)를 별도 권한으로 두는 정책을 네 경로 전부에 같은 강도로
* 적용한다 — 어느 경로로 들어오느냐가 권한 경계를 바꾸면 그 경로가 우회로가 된다.
*
* 검토 단계에서는 "번들은 저장소 동봉 신뢰 자산이라 임의 PHP 반입 경로가 아니다" 를 근거로
* 면제했으나, PO 가 네 경로 일관 적용을 선택했다. 그 결과 설치 권한만 가진 운영자가 활성
* 면제했으나, 네 경로에 일관 적용하는 것으로 결정했다. 그 결과 설치 권한만 가진 운영자가 활성
* 팩을 재설치하면 그 팩이 `installed` 로 내려가고, 다시 켜려면 관리 권한이 필요하다.
*
* @scenario vector=install_from_bundled, actor_permission=install_only
@@ -38,7 +38,7 @@ class DevtoolsRouteCacheTest extends TestCase
* 덤프 위임이 실제로 수행됐는지 확인할 때 쓰는 섹션.
*
* 하네스 자식 프로세스는 실 `storage/debug-dump` 에 쓴다 (경로가 `storage_path()` 고정).
* PO 가 브라우저에서 받아 둔 덤프를 테스트가 덮어쓰지 않도록 이 파일만 스냅샷·복원한다.
* 개발자가 브라우저에서 받아 둔 덤프를 테스트가 덮어쓰지 않도록 이 파일만 스냅샷·복원한다.
*/
private const PROBE_SECTION = 'logs';
@@ -83,7 +83,7 @@ class DevtoolsRouteCacheTest extends TestCase
self::removeDirectory(dirname(__DIR__, 3).'/'.self::$cacheRelDir);
}
// PO 의 덤프 데이터를 원상 복구 (자식 프로세스가 실 storage 에 썼다).
// 개발자의 덤프 데이터를 원상 복구 (자식 프로세스가 실 storage 에 썼다).
$probePath = dirname(__DIR__, 3).'/storage/debug-dump/'.self::PROBE_FILE;
if (self::$probeExisted && self::$probeBackup !== null) {
file_put_contents($probePath, self::$probeBackup);
@@ -7,7 +7,7 @@
* 풀린 상태로 `/admin/` 에 접근하면 대시보드 → 로그인 리다이렉트 흐름 대신 404 가
* 노출됐다. 수정: `Router.match()` 진입 시 끝 슬래시를 정규화한다.
*
* 단위 테스트(Router.test.ts)가 매칭 로직을 잠그지만, PO 가 실제로 브라우저에서
* 단위 테스트(Router.test.ts)가 매칭 로직을 잠그지만, 실제 브라우저에서
* 재현한 증상이므로(단위 green + 브라우저 깨짐 방지) 브라우저 레벨로도 잠근다.
*
* @scenario admin_trailing_slash_unauthenticated + admin_no_trailing_slash_unauthenticated
@@ -32,7 +32,8 @@
* 단위 테스트는 이 결함을 잡지 못한다 — React 렌더 사이클과 복수 루트 렌더러의 effect 실행
* 순서를 모사하지 못하기 때문이다.
*
* @see .claude/docs/frontend/troubleshooting-state-global.md 사례 5
* 회귀 배경: 탭 왕복 후 initLocal 동기화가 편집 중이던 입력칸의 값을 되돌려, 먼저 편집한
* 입력칸에만 옛 값이 남던 결함 (engine-v1.54.7 에서 수정).
*/
import { test, expect, issueToken, issueScopedToken, authenticatePage } from '../../fixtures/auth';
import type { Page } from '@playwright/test';
@@ -79,7 +79,7 @@ effects:
# E2E axis 없음: 판정은 전부 서버측(FormRequest / Service)이다. 관리자 화면에서
# auto_activate 를 보내는 곳은 번들 재설치 모달 하나뿐이고, 게이트를 네 경로 전부에
# 걸기로 하면서(PO 결정 2026-08-04) 이 모달이 보내는 값을 운영자의 활성화 권한
# 걸기로 하면서(프로젝트 결정 2026-08-04) 이 모달이 보내는 값을 운영자의 활성화 권한
# (`abilities.can_activate`)으로 분기하도록 바꿨다 — 권한이 없으면 auto_activate 를
# 보내지 않아 재설치는 그대로 되고 팩만 installed 로 내려간다.
#
@@ -11,7 +11,7 @@ description: |
설치 행은 있으나 파일이 사라진 드리프트 팩도 함께 복구한다(수동 복구 경로 겸용).
- list()/Resource 는 파생 플래그 files_missing(active 인데 설치본 부재)과
bundled_source_available(번들 소스로 복구 가능)을 부여해 드리프트 표면화 + 복구 가능 여부를 구분한다.
- A2(인스톨러 기본 선택)는 admin_language 가 ko/en 한정이라 no-op → PO 결정으로 제외.
- A2(인스톨러 기본 선택)는 admin_language 가 ko/en 한정이라 no-op → 프로젝트 결정으로 제외.
axes:
target_locale_source: [supported_locales_default, explicit_locale_option]
@@ -40,15 +40,12 @@ effects:
- cli_list_surfaces_uninstalled_and_drift
- admin_list_shows_drift_badge_and_reinstall
- third_party_pack_without_bundle_source_not_offered_for_reinstall
- supported_locale_without_bundle_source_is_statically_flagged
- declared_locale_without_installed_pack_is_statically_flagged
test_files:
- tests/Feature/LanguagePack/ProvisionLanguagePackCommandTest.php
- tests/Feature/LanguagePack/ListDriftDetectionTest.php
- tests/Feature/LanguagePack/ListLanguagePackCommandTest.php
- templates/_bundled/sirsoft-admin_basic/__tests__/layouts/admin-language-pack-drift.test.tsx
- .claude/scripts/audit/__tests__/rules.test.cjs
e2e_files:
- tests/Playwright/specs/admin/language-pack-drift-discovery.spec.ts
@@ -57,9 +54,15 @@ rules_layer_coverage:
- rule: service-direct-data-access
coverage: ProvisionLanguagePackCommand 은 LanguagePackService 만 주입 — Model/DB facade 직접 접근 없음
- rule: lang-pack-supported-locale-has-bundle
coverage: config/app.php supported_locales 비-base 로케일 ↔ g7-core-{locale} 번들 소스 정합 정적 검사
coverage: |
config/app.php supported_locales 비-base 로케일 ↔ g7-core-{locale} 번들 소스 정합 정적 검사.
효과 supported_locale_without_bundle_source_is_statically_flagged 를 담당한다 — 런타임
테스트가 아니라 정적 검사 계층에서만 성립하므로 effects 목록이 아닌 이 항목이 SSoT 다.
- rule: lang-pack-installed-version-drift
coverage: 선언 로케일의 설치본 부재(미설치 사각)를 프로비저닝된 환경에 한해 정적 검사
coverage: |
선언 로케일의 설치본 부재(미설치 사각)를 프로비저닝된 환경에 한해 정적 검사.
효과 declared_locale_without_installed_pack_is_statically_flagged 를 담당한다 — 위와 동일한
이유로 effects 목록이 아닌 이 항목이 SSoT 다.
- rule: frontend-change-requires-e2e
coverage: 관리자 언어팩 목록 partial 변경에 동반하는 language-pack-drift-discovery.spec.ts (템플릿 도메인)