503 Commits
Author SHA1 Message Date
HeuJung b07d6c8953 fix(ecommerce,tosspayments,templates): 에스크로 표시 누락·결제수단 전환 시 입력 잔류 수정
- 에스크로 표시가 영구히 렌더되지 않던 문제: is_escrow 가 DB·모델·부분취소 차단
 로직에는 있으나 OrderPaymentResource 가 내보내지 않아, 이 값을 참조하는 주문
 상세(관리자·구매자)의 에스크로 행이 항상 false 로 평가됐다. Resource 에 노출한다.

- 결제수단을 바꿔도 이전 수단의 입력이 주문에 실리던 문제: 결제수단 버튼이
 _local.paymentMethod 만 바꾸고 슬롯이 소유한 확장 페이로드·환불계좌는 비우지
 않았다. 확장 슬롯과 환불계좌 블록은 결제수단 조건부로 언마운트되지만 _local
 값은 남으므로, 무통장에서 현금영수증·환불계좌를 입력한 뒤 카드로 전환하면 그
 값이 주문 생성 요청에 그대로 실렸다.
 프론트는 결제수단 전환 시 슬롯 소유 상태를 초기화하고, 서버는 환불계좌가 쓰이지
 않는 결제수단이면 저장하지 않도록 게이팅한다(현금영수증은 이미 dbank 게이트 보유).

- Modal 이 받지 않는 size prop 을 쓰던 4곳을 width 로 교체(무시되어 의도한 폭이
 나오지 않았다). 토스 에러 모달의 setState target 을 params 안으로 옮긴다.

- 레이아웃 편집기 팔레트에서 체크박스를 넣으면 라벨 없이 네모 칸만 놓이던 문제:
 Checkbox 는 label prop 을 렌더하지 않으므로 Label 래핑 + Span 텍스트로 바꾸고,
 Accordion 이 Label 을 받도록 중첩 스펙을 맞춘다.
2026-08-06 13:58:00 +09:00
HeuJung 38a5cbe550 fix(tosspayments,pay_kginicis): 플러그인 설정 검증이 큐로 나가 무동작하던 문제 수정
설정 저장을 차단해야 할 검증 리스너가 core.plugin_settings.before_save 를
구독하면서 sync 를 선언하지 않았다. Action 훅의 기본값은 큐 디스패치이고
실환경 큐 드라이버는 database 이므로, ValidationException 이 워커 안에서 죽고
PluginSettingsService::save 는 doAction 직후 저장을 그대로 진행했다.

결과적으로 범위 밖 가상계좌 유효시간, 허용값 밖 에스크로 설정, 빈 CBT 라이브
필수값이 아무 저항 없이 저장됐다. 리스너 docblock 이 스스로 약속한 422 응답도
나가지 않았다.

두 리스너에 sync 를 선언해 저장 트랜잭션 안에서 동기 실행되도록 했다.

회귀 가드는 훅 디스패치 경로를 실제로 통과시킨다. phpunit.xml 이 큐 드라이버를
sync 로 강제해 큐 경로도 즉시 실행되므로, Queue::fake 로 운영과 같은 비동기
상황을 만들어야 결함이 드러난다 — 리스너 메서드를 직접 호출하는 기존 테스트는
이 결함을 구조적으로 잡지 못한다.
2026-08-06 13:57:57 +09:00
HeuJung 403154167a fix(core): api:docgen 재생성이 문서를 손실·열화시키는 결함 수정
재생성이 사람이 채운 서술과 이전 실측 결과를 지우고, 코드가 바뀌지 않은
문서까지 매 실행마다 흔들던 결함 5건을 고쳤다.

- 중첩 객체 파라미터가 문서에서 통째로 누락되던 문제: 배열 요소(items.*.id)를
 상위로 대표시키려던 스킵 조건이 점(.) 포함 여부만 봐서, 와일드카드가 없는
 중첩 객체 필드(refund_bank.*, general.*, content.* 등 17개 엔드포인트 수백 개)
 까지 함께 버렸다. 와일드카드가 있을 때만 스킵하도록 좁혔다.
- 사람이 보강한 에러 응답 표가 자동 추론 초안에 덮여 사라지던 문제:
 상태코드 키 단위로 병합한다.
- 실측 실패가 이전에 관측해 둔 응답 예시를 지우던 문제: 실측 성패는 호출 시점
 데이터 유무에 좌우되므로(DELETE /checkout 은 대상이 없으면 404), 실패는
 "새로 관측하지 못했다"는 뜻이지 기존 관측이 무효라는 뜻이 아니다.
- 실측 예시값·응답 예시 JSON 이 DB 상태를 그대로 반영해 비멱등하던 문제:
 값이 아니라 스펙(필드 집합·타입)이 바뀐 경우에만 갱신한다. nullable 이
 null 로 관측된 것은 타입 변경이 아니므로 기존 값을 유지한다.
- 요청 예시 path 파라미터가 실측 성패에 따라 흔들리던 문제: placeholder 로 고정.

결과: 문서 63개 전수 대조에서 손실 0(서술·표 셀·응답 예시), 전 scope 연속
재생성 시 diff 0(멱등).
2026-08-06 13:54:35 +09:00
HeuJung 3f2674f1a7 fix(tosspayments,templates,core): 결제수단 체크박스 라벨 미표시·저장 실패 수정
Checkbox 는 label prop 을 렌더하지 않는다(부모 Label 이 표시 담당). 그 prop 에만
의존해 결제수단 9종의 라벨이 화면에서 전부 비어 있었다. Label 로 감싸고 텍스트를
형제 Span 으로 분리했다 — 라벨 클릭 토글과 접근성 이름도 함께 확보된다.

수정 중 저장 결함을 함께 발견했다. 폼 자동바인딩은 값이 boolean 일 때만 checked 로
붙고, null 이면 value 분기로 떨어져 빈 문자열을 전송한다. 서버가 그것을 null 로
저장하면 기본값(false)을 덮어 화면에서 다시 켤 수 없게 고착된다. initLocal 로는
막을 수 없어(shallow 는 form 객체를 교체하고, deep 은 source 의 null 을 그대로
반영) 레이아웃이 boolean 을 직접 보장하도록 했다 — autoBinding:false + checked 강제
+ change 액션이 $event.target.checked 를 기록.

같은 원인으로 편집기 팔레트도 라벨 없는 체크박스를 삽입하고 있어 함께 고쳤다.

회귀 방어: 레이아웃/팔레트 계약 테스트, 엔진 동작(isCheckedBinding·deepMergeState)
고정, 저장 페이로드가 boolean 인지 검사하는 E2E, audit 룰
layout-checkbox-label-and-boolean(label prop=error, 자동바인딩 의존=warn).
2026-08-06 13:27:45 +09:00
HeuJung faa79b0ae1 feat(tosspayments,pay_kginicis,ecommerce,core): 현금영수증 프로바이더 훅 축 + 토스 환불 정합성
현금영수증 발급사를 PG 결제사와 독립적으로 선택할 수 있도록 공용 훅 축
(registered_providers/issue/cancel)에 토스·KG 프로바이더를 등록한다. KG 결제로
받으면서 영수증만 토스로 발급하는 구성이 가능해진다. KG 전용 발행 컨트롤러·
라우트·관리자 패널은 이 축으로 이전하며 제거했다 — 이중 발행 경로를 남기지
않기 위해서다.

토스 환불은 네 가지를 바로잡았다. 전액취소를 부분취소로 보내던 문제(R1),
면세금액 미전달로 복합과세 상점의 부가세가 틀어지던 문제(R2), 가상계좌
환불계좌를 싣지 않아 취소가 실패하던 문제(R3), 에스크로 부분취소를 PG 호출
전에 차단하지 않던 문제(E4). 취소·발급 요청에는 멱등키를 부착해 네트워크
재시도가 국세청에 이중 신고되거나 이중 환불되는 것을 막는다.

코어 활동로그 가드가 \Exception 만 잡아 채널 해석 실패(\Error)를 놓치고 있었다.
결제 입금통보 같은 외부 콜백이 로그 문제만으로 500을 반환하던 문제를 \Throwable
로 수정한다. 활동로그는 부수 기록이므로 그것을 유발한 요청까지 죽여서는 안 된다.

토스 API 서비스는 베이스 \Exception 을 직접 던지고 있어 도메인 예외
(TossPaymentsApiException)를 신설했다. KG의 KgInicisApiException 선례와 동형이며
RuntimeException 상속이라 기존 소비자의 catch 는 그대로 동작한다.
2026-08-06 12:58:59 +09:00
HeuJung a9f85ff92b feat(tosspayments,ecommerce,core): 토스 주문서형 결제수단·가상계좌·에스크로 + API 문서 web 라우트 확장
토스페이먼츠 플러그인을 프로덕션 수준으로 완성한다 (계획서 S4).

주문서형 결제(9종 동적등록)
- 플러그인이 filter_available_payment_methods 로 결제수단을 주입하면 체크아웃에
 독립 항목으로 노출된다. 서버 전송 시에는 core_payment_method 로 번역해 보낸다 —
 코어 PaymentMethodEnum 은 toss_* 를 거부하므로 번역 없이는 주문 자체가 422 로 실패했다.
 번역 근거인 core_payment_method 가 설정 병합/스냅샷의 화이트리스트에서 탈락하던 것을
 provider-agnostic 하게 보존하도록 고쳤다 (토스 전용 분기 없음).

가상계좌·웹훅·에스크로
- 입금통보/결제상태 웹훅 2종 신설. secret 대조(hash_equals)·리플레이 멱등·CSRF 면제.
- 에스크로 3상태(off/on/buyer_choice). escrowProducts 를 가상계좌·계좌이체 양쪽
 SDK 페이로드에 싣는다 — 서버가 조립해도 SDK 로 전달되지 않으면 토스가 결제를 거부한다.
 E2E 가 SDK 경계를 직접 캡처해 이 계약의 나머지 절반을 잠근다.
- 관리자 설정 저장 시 입금기한·에스크로 값 범위를 서버에서 검증한다 (UI max 는 클라 힌트라
 API 직접 호출을 막지 못한다).

레이아웃 리스너 실행 순서
- KG 는 no-PG 리스트를 통짜 리터럴로 str_replace 하므로 토스가 먼저 append 하면 KG 의
 매치가 깨진다. HookManager 는 ksort 오름차순이므로 토스 priority 를 30 으로 두어
 "KG(20) 먼저" 를 플러그인 로드 순서와 무관한 불변식으로 고정했다.

코어 — API 문서 생성기가 확장 web 라우트도 수집
- PG 콜백·웹훅은 CSRF/세션 특성상 web.php 에 등록되지만 외부 시스템이 호출하는
 machine-facing 엔드포인트라 API 레퍼런스 대상이다. api/ prefix 만 수집하던 탓에
 영구 무문서였다. 확장 소유 web 라우트를 수집하되 관리자 화면(admin 컨텍스트)은 제외한다.
 코어 라우트 수집 결과는 291건으로 불변.

하네스 — 확장 테스트 베이스클래스 룰 신설
- coverage.json 의 test-extension-base-class 가 status:todo(룰 미구현)라 규정이 있어도
 검출되지 않았고, 실제로 이번 신설 테스트가 그 사각에 빠졌다. 룰을 구현해 봉인한다.
2026-08-06 12:46:48 +09:00
HeuJung 42eb21320b fix(ecommerce,core): 현금영수증 UI 보완 + 하네스 결함 6건 + 확장점 룰 사각 제거
현금영수증/환불계좌 기능(S3)의 감사 보완과, 그 과정에서 발견한 하네스 결함을
같은 세션에서 일괄 처리한다. 지시에 따라 무관 산출물(namuwiki 문서 초안 포함)도
단일 커밋으로 묶는다.

- 현금영수증 발급 이력 접기/펼치기(W-3) + 결제행별 독립 토글 + aria 상태
- vendor-bundle manifest 해시 재생성 — composer.json version bump 미반영분 정합
- 시나리오 매니페스트 거짓 경로 정정(A/B) + 미작성 부채 :test-files 정직 표기(C)
- layout-* 룰의 resources/extensions 검사 사각 제거 + 확장점 전용 root 스키마 분기
- validate-test-scenario.cjs: 맵형 test_files 크래시 + fallback 파서 covers 오검출 수정
- tosspayments 확장점 setState target 규약 위반 수정
2026-08-06 12:36:31 +09:00
HeuJung 58560eefb1 feat(ecommerce): 현금영수증 발급 API + 입금완료 자동발급 + 금액변동 연동
무통장입금 주문의 현금영수증 발급을 관리자·회원·비회원 세 경로에서 제공하고,
입금이 확인되면 자동 발급하며, 부분환불로 금액이 바뀌면 전액취소 후 재발급한다.

발급 프로바이더에 종속되지 않도록 발급/취소는 필터 훅으로 위임한다.
자동발급 리스너는 결제완료 전이(after_payment_complete)와 입금만 기록
(after_deposit_recorded) 두 경로를 모두 구독한다 — 후자가 없으면 관리자가
"입금만 기록"했을 때 자동발급이 통째로 누락된다.

금액 동기화(syncFromOrder)는 트랜잭션이 닫힌 뒤에 부른다. 재발급 실패 이력은
국세청 신고 누락을 막는 원장이라 롤백에 휩쓸리면 안 되고, 프로바이더 왕복 2회
동안 DB 잠금을 붙들 수 없기 때문이다. 환불은 이미 확정됐으므로 어떤 실패도
호출부로 전파하지 않고 FAILED 이력 + 로그로 남겨 관리자 수동 복구에 맡긴다.

식별번호는 재발급용으로만 암호화 보관하고 구매확정 시점에 폐기한다.
그 시점을 잡기 위해 order.after_purchase_confirmed 훅을 신설했다.

함께 고친 사전 결함:

- 취소 시 total_cash_equivalent_amount 가 재계산되지 않아 syncFromOrder 가
 "금액 변동 없음"으로 판정, 부분환불 후 재발급이 아예 동작하지 않았다.
 산정 규칙을 PaymentMethodEnum::resolveCashEquivalentAmount 로 SSoT 화해
 주문 생성 시점과 취소 재계산 시점이 같은 규칙을 쓰게 했다.

- 현금영수증 발급이 payment.receipt_url 을 덮어써 카드 매출전표 URL 이
 유실됐다. 한 컬럼이 두 의미를 갖던 문제로, 현금영수증 URL 은 이력 테이블이
 보관하도록 분리했다.

- OrderService::confirmOption 이 confirmed_at 을 무조건 now 로 덮어썼다
 (v0.16.1 부터 존재). 재확정 시 최초 확정 시점이 소실되고 구매확정 훅이
 중복 발화한다. OrderOptionService 와 대칭으로 멱등 가드를 넣었다.

- 코어 api:docgen 이 사람이 작성한 API 문서를 재생성 때마다 지웠다.
 @generated 블록 안의 표 셀·응답 본문이 대상이었고, extractGeneratedBlock
 이 앞의 `## ` 헤딩까지 거슬러 올라가 여러 엔드포인트를 한 덩어리로 반환하던
 문제도 함께 고쳤다. 실측 산출 응답 예시에 @probed 출처 마커를 붙여, 마커가
 없는 본문은 사람 작성분으로 보고 보존한다 — 실측 1회의 표본은 사람이 코드에서
 읽어낸 사실보다 좁으므로 실측은 기본값이지 덮어쓰기 권한이 아니다.

 ParameterDescriber 가 {order} 같은 path 파라미터를 "정렬 방향 asc/desc" 로
 설명하던 오염도 정정했다.
2026-08-06 12:17:24 +09:00
HeuJung eb724ed348 test(ecommerce): 통화 스냅샷 부재 테스트가 팩토리 기본값에 의존하던 문제 수정
test_mc_fields_null_without_currency_snapshot 은 "OrderFactory 가 currency_snapshot 을
비워 둔다"는 전제로 작성됐으나, 이후 팩토리에 기본 스냅샷이 추가되면서 전제가 무너져
클린 트리에서도 실패하고 있었다. 비즈니스 로직 회귀가 아니라 stale test 다.

팩토리 기본값에 의존하지 않고 currency_snapshot 을 명시적으로 null 로 주입해
레거시 주문(다통화 도입 이전 행)의 취소 경로를 재현한다. 전제가 다시 조용히
무너지지 않도록 assert 로 고정했다.

전액취소 경로의 스냅샷 가드를 무력화하면 이 테스트가 실패하는 것을 확인했다.
2026-08-06 11:50:52 +09:00
HeuJung cd82fe3fb4 feat(ecommerce): 현금영수증 발급 회차를 훅 페이로드로 제공
토스페이먼츠는 동일 orderId 로 재발급을 요청하면 중복으로 거부한다.
계획서 §B-8 은 "{order_number}-cr{seq}" 형태의 회차 접미를 요구하는데,
기존 발급 훅 페이로드에는 order_number 원본만 있어 리스너가 회차를 알 수 없었다.

회차는 코어가 센다. 프로바이더 리스너가 이력 테이블 구조를 알 필요가 없고,
향후 팝빌·바로빌 등 다른 발급사도 같은 숫자를 그대로 쓸 수 있다.
프로바이더 종속 문자열 조립은 리스너 내부에만 둔다.

실패한 발급도 회차를 소모한다 — 프로바이더에 요청이 도달한 뒤 응답만 실패한
경우 같은 식별자를 재사용하면 중복 거부되기 때문이다. 취소 이력은 소모하지 않는다.
2026-08-06 11:48:13 +09:00
HeuJung af1da5fe0b feat(ecommerce): 현금영수증 프로바이더 훅 축 + 이력 테이블 + 현금성 금액 + 배송비 과세 정책
현금영수증 발급 프로바이더를 PG 와 독립적으로 선택할 수 있도록 훅 축을 신설한다.
KG이니시스로 결제받으면서 현금영수증만 토스페이먼츠로 발급하는 구성이 가능하다.

발급/취소 원장(ecommerce_order_cash_receipts)이 국세청 신고 근거를 보관한다.
금액이 바뀌면 syncFromOrder 가 주문의 현재 상태에서 발급액을 재계산해
전체취소 → 전액 재발급한다. 부분취소 API 를 쓰지 않으므로 전액취소만 지원하는
벤더도 동일 인터페이스로 수용되고, 증가/감소 방향을 구분하지 않아 향후 반품·교환
배송비 청구가 도입되어도 수정 없이 동작한다.

배송비는 그동안 과세/면세 분류에서 아예 빠져 있었다. 배송비는 단계 3 에서 계산되고
할인은 단계 4 에서 적용되므로 상품 분류 시점(단계 2-b)에는 존재하지 않는다.
따라서 할인 후 배송비가 확정된 Summary 집계 직전에 3가지 정책 중 하나로 분류해
합산하고, 옵션별 과세액에는 배송비를 섞지 않는다.

재발급용 식별번호는 APP_KEY 기반으로 암호화 보관하고 구매확정 시 폐기한다.
이력에는 마스킹 값만 남기며 프로바이더 원응답의 민감 키도 가린다.
주민등록번호는 수집하지 않는다.

cash_receipt_type 은 Enum 캐스트하지 않는다 — 레거시 값(income_deduction)이 남아
있는 동안 Laravel 의 ::from 이 ValueError 를 던져 정규화해야 할 업그레이드 스텝
자신이 그 행을 읽지 못하게 되기 때문이다.
2026-08-06 11:48:12 +09:00
HeuJung 5b587d7119 fix(audit): api-doc-coverage 가 web 전용 확장에 문서 동반을 요구하던 오탐 수정
토스페이먼츠 복원 후 api-doc-coverage 가 warn 4건을 냈으나, 토스는 src/routes/api.php
가 없고 web.php 의 브라우저 리다이렉트 콜백 2건만 가진다. api:docgen 은 이 경우
"API 라우트가 없습니다" 로 응답하므로 문서 생성 자체가 불가능하다. 즉 충족할 수 없는
위반을 요구하고 있었다.

원인은 룰의 appliesTo 가 routes/*.php·Controllers/** 를 파일 경로로만 매칭하고 그
확장이 실제 api/ 표면을 갖는지 확인하지 않은 것이다. 확장의 api/ prefix 라우트는
RouteServiceProvider 가 api.php 를 로드해서만 등록되므로(web.php 는 web prefix),
api.php 존재 여부가 필요충분 판정자다. 번들 확장 14개 전수 대조에서 docs/api 보유와
api.php 존재가 14/14 일치함을 확인했다.

extensionHasApiSurface 게이트를 두어 api.php 가 없는 확장을 check 단계에서
면제한다. 확장명은 하드코딩하지 않으므로 api.php 가 추가되면 자동으로 검사 대상이
된다. repoRoot 미주입 시에는 판정이 불가하므로 기존대로 보수적으로 보고한다.

같은 오탐이 gnuboard7-hello_plugin 에도 있었고 함께 해소된다. api.php 를 가진
pay_kginicis 는 web.php 만 바뀌어도 여전히 문서 동반을 요구한다(면제 오적용 방지).

규정과 도구를 함께 갱신했다 — api-documentation.md 에 "문서 대상이 아닌 확장" 절을
신설하고, coverage.json 의 stale 한 note(verification_kginicis 를 미문서화 대상으로
기술)를 실제 상태로 정정했다.
2026-08-06 11:32:52 +09:00
HeuJung 26a349590e feat(plugins): 토스페이먼츠 번들 플러그인 복원
7.0.0 정식 출시 커밋에서 PG 3종이 함께 제거되었으나, 그중 토스페이먼츠만
번들로 되돌린다. 삭제 직전 트리를 기준으로 플러그인 40개 파일과 일본어
언어팩 6개 파일을 복원하고, 삭제에 연동되어 변경되었던 README 목록·언어팩
빌드 스크립트 엔트리·이커머스 편집기 샘플의 PG provider 를 함께 되살렸다.

복원된 코드는 삭제 시점에 멈춰 있어 현행 코드베이스와 어긋나는 지점이 있어
함께 정합화했다.

- PluginTestCase 가 활성 디렉토리 경로를 참조해 _bundled 단독 실행 시
 라우트가 등록되지 않던 문제를 자기 기준 경로로 교체
- SettingsServiceProvider 의 site_url override 로 app.url 이 오염되어
 assertRedirect 가 간헐 실패하던 문제를 APP_URL 명시 리셋으로 차단
- 환경설정 레이아웃의 시맨틱 클래스가 한 칸씩 밀려 순환 배치되어 2열 폼이
 무너지고 섹션 제목이 에러 색으로 표시되던 결함을 정정 (회귀 테스트 신설)
- 편집기 저장 시 silent drop 되던 미표준 root 키 제거, Icon 을 size prop 으로 전환
2026-08-06 11:29:47 +09:00
정정홍andGitHub 2b2856ed37 Merge pull request from gnuboard:HeuJung/issue519
HeuJung/issue519
2026-08-06 11:17:37 +09:00
HeuJung bd7b31feb8 fix(ecommerce): 상품 카드 별점이 리뷰 유무와 무관하게 0 으로 보이던 문제
최근 본 상품과 찜 목록만 리뷰 집계를 붙이지 않아, 리뷰가 달린 상품도 카드에 별 0 개로
표시됐다. 두 조회에 다른 목록과 같은 집계를 붙였다.

값이 비었는지로 판정하던 소비 측도 함께 고쳤다. 집계를 안 한 것과 세어보니 0건인 것이
같은 0 으로 뭉개져 있었다 — 이제 집계 컬럼이 붙었는지로 판정해, 세지 않은 조회에서는
항목을 생략하고 리뷰가 0건이면 종전대로 0 으로 표기한다. 관리자 상품 목록이 그 대상이라
API 문서에 어느 조회가 통계를 싣는지 명시했다.

소비처를 필드명으로만 훑어 처음에는 "화면에 안 보인다" 고 판정했는데 틀렸다. 레이아웃은
상품 객체를 통째로 카드에 넘기고 카드가 내부에서 별점을 그린다. 그 컴포넌트를 쓰는 화면의
공급 경로를 전수로 세어 찜 목록을 추가로 찾았다.

develop 리베이스 후속 검증 결과와 충돌 해소 근거도 함께 담는다.
2026-08-06 11:13:54 +09:00
HeuJung 7b859d4200 fix(core,basic,ecommerce): 쇼핑 화면 호출 통합 원복·검색 주소 승계·라우트 캐시 갱신 및 상한값 설정 저장 정합
합의되지 않은 구조 변경을 되돌리고, 계획서 전수조사에서 드러난 결함을 처리했다.

쇼핑 화면 호출 통합(F4)은 API 쿼리 튜닝이 아니라 화면 구조 변경이라 원복했다.
레이아웃 9개를 develop 상태로 복원하고 전용 엔드포인트·컨트롤러·문서·테스트를 걷어냈다.
되돌리면 함께 사라지는 비-F4 수정 둘(페이저 has_more_pages 판정 3곳, Icon 크기 클래스
23줄)은 재적용했다. 성능 개선분은 전부 잔존한다.

통합검색 화면 상태를 전역 상태에서 URL 쿼리로 옮겼다. 종전에는 탭·필터·정렬·페이지가
주소에 없어 새로고침·뒤로가기에서 초기화되고 결과를 공유할 수도 없었다. 화면은 정상으로
보이고 콘솔 에러도 없어 코드만으로는 드러나지 않는 형태였다.

확장이 라우트를 추가해도 라우트 캐시가 걸린 사이트에서는 그 주소가 404 가 되던 구멍을
막았다. 훅 캐시와 달리 라우트 캐시에는 스캔 폴백이 없어 예외도 경고도 남지 않는다.
같은 문제를 이미 푼 ConfigCacheHelper 를 미러링해 갱신 지점을 한 곳으로 모았고,
코어 업데이트가 캐시를 비우기만 하고 되살리지 않던 문제도 함께 고쳤다.

상품·페이지 검색이 커서 판정에 페이지 번호를 넘기지 않아, 커서 없이 깊은 페이지를
지목한 딥링크가 조용히 1 페이지를 돌려주던 결함을 고쳤다. 호출 지점을 손으로 열거하는
대신 저장소를 훑는 패리티 테스트로 다음 도메인의 재발을 막는다.

환경설정 고급 탭의 목록 상한값이 저장되지 않던 결함을 고쳤다. 화면·검증·읽기는 모두
있었는데 저장 시 카테고리 분류표가 손으로 열거돼 있어 그 값만 버려지고 있었다. 분류표를
설정 정의에서 도출하도록 바꿔 다음 카테고리가 합류해도 같은 일이 생기지 않게 했다.
범위를 벗어난 값의 안내에 내부 식별자가 노출되던 것도 함께 정리했다.

언어팩 재설치가 활성 팩을 자기 자신과의 슬롯 충돌로 오인해 강등하던 회귀와, CLI 설치가
HTTP 권한 컨텍스트 없이 자동 활성화되는지를 고정하는 테스트를 함께 담았다.
2026-08-06 11:13:54 +09:00
HeuJung 0d1105abfc fix(basic,admin_basic): 상한 목록의 페이저 소비단 정합 및 화면 표기
저장소가 총 건수를 상한까지만 세게 되면서 마지막 페이지 번호가 없는 목록이
생겼는데, 화면은 그 값을 1 로 접거나 페이저 노출 조건으로만 쓰고 있었다. 앞은
"1페이지뿐" 으로 잘못 말하고 뒤는 페이저를 통째로 지워 뒤쪽 페이지로 갈 방법
자체를 없앤다. 둘 다 예외도 404 도 내지 않아 화면은 정상으로 보이고 기능만 없다.

Pagination 컴포넌트가 세 형태를 모두 이해하도록 넓혔다. 마지막 페이지를 알면
페이지 산술이 우선이고, 모를 때만 다음 페이지 존재 여부로 판정한다. 처음·마지막
버튼을 따로 켜고 끌 수 있게 나눠, 마지막 페이지를 모르는 목록에서는 그 버튼만
감춰지고 나머지 이동은 그대로 열려 있다. DataGrid 는 페이저를 그리는 두 경로
모두에 그 값을 전달한다 — 한쪽만 전달하면 그 화면만 첫 페이지에 갇힌다.

마이페이지 주문·마일리지·위시리스트·알림함, 게시판 목록, 상품 리뷰·문의, 내가 쓴
글, 관리자 설정 탭과 스케줄 이력의 소비 지점을 전건 옮겼다.

검색 화면은 총 건수를 정확히 세지 못한 경우 "N건 이상" 으로 표기하고 검색어를
좁히면 정확한 건수를 볼 수 있다고 안내한다. 탭 배지도 세지 못한 값을 정확한
숫자처럼 보여주지 않는다. 최신순은 첫 페이지부터 커서로 이동하며, 주소로 특정
페이지를 지목한 딥링크는 종전 동작을 유지한다.

쇼핑 첫 화면과 분류 화면을 단일 요청으로 재배선하고, 섹션 partial 이 부모가
선언한 이름을 읽도록 통일했다. 데이터소스 이름에 직결돼 있으면 다른 화면이
재사용할 때 폴백에 걸려 예외도 로그도 없이 섹션이 사라진다. 상품 상세의 "최근 본
상품" 이 계속 비어 있던 것은 존재하지 않는 라우트 파라미터를 읽고 있어서였다.

게시판 목록 세 스킨의 블라인드 아이콘 대비와 이미지 위 공통 색의 다크 모드 표기를
정리했다.
2026-08-06 11:13:53 +09:00
HeuJung 74a0a54016 fix(gdpr): 상한 목록에서 페이저가 접히던 자리 정합
동의 이력과 정책 버전 발행 이력의 페이저가 마지막 페이지 번호 하나에 의존하고
있었다. 총 건수를 끝까지 세지 못한 목록에서는 그 값이 없어, 페이저 노출 조건이
거짓이 되어 통째로 사라지거나 "다음" 이 첫 페이지부터 비활성이었다.

마지막 페이지를 아는 경우의 동작은 종전과 같고, 모르는 경우에만 다음 페이지
존재 여부로 판정한다. 표시용 마지막 페이지 번호는 지어내지 않는다.
2026-08-06 11:13:52 +09:00
HeuJung e619b21b2b perf(page): 검색 전량 적재 제거 및 상한 계약 소비
검색이 조건에 맞는 페이지 전체를 읽어 온 뒤 PHP 에서 화면 분량만 잘라 내고
있었다. 실제 page/per_page 를 저장소까지 하달해 필요한 분량만 조회한다.
구체 엔진 직접 호출도 해석기 경유로 옮겨 플러그인 검색엔진이 페이지 검색에
적용된다.

관리자 목록은 화면에 본문이 나오지 않는데도 항목마다 본문을 함께 읽고 있었다.
목록 조회에서 제외하고 총 건수에 상한 계약을 적용했다.

발행 페이지 목록과 통합검색의 페이지 탭 정렬에 필요한 색인을 더했다. 신규 설치는
마이그레이션이, 기존 사이트는 업그레이드 스텝이 적용한다.

관리자 목록의 페이저가 총 건수를 끝까지 세지 못한 경우 "1페이지뿐" 으로 접혀
뒤쪽 페이지에 도달할 수 없던 것을 고쳤다.
2026-08-06 11:13:52 +09:00
HeuJung 4c62dbe649 perf(ecommerce): 진열·집계·검색의 반복 실행 제거 및 상한 계약 소비
집계 — 판매량 정렬과 인기 상품이 ORDER BY 안의 상관 서브쿼리라 LIMIT 이 평가
범위를 막지 못했다. 전 상품을 평가한 뒤 정렬하던 것을 파생 테이블 조인으로 바꿨다.
리뷰 개수·평점도 상품마다 리뷰 표를 두 번씩 훑던 것을 한 번의 그룹 집계로 합쳤다.
대표 이미지는 이미 적재된 관계를 쓰지 않고 매번 관계 빌더로 조회해 상품당 2회씩
쿼리를 더 내고 있었다.

루프 안 쿼리 — 체크아웃의 항목별 옵션 조회, 게스트 장바구니 병합의 항목별 2쿼리,
주문 옵션의 항목별 find 를 일괄 조회로 옮겼다. 상품 대량 가격·재고 수정은 행마다
저장을 반복했는데, 두 번째 인자를 넘기던 오용 때문에 실제로는 바인딩이 어긋나
실행 자체가 실패하고 있었다. 이 두 메서드를 호출하는 테스트가 0건이라 전 스위트
green 상태로 통과하고 있었다.

카테고리 트리는 노드마다 집계 쿼리를 재귀 발행하던 것을 평면 1쿼리 + 조립으로
바꾸고 캐시했다. 거의 모든 쇼핑 화면이 읽는 값이라 분류·상품 변경 시 무효화한다.

검색 — 조건에 맞는 ID 전량을 PHP 로 적재한 뒤 무제한 whereIn 으로 다시 조회하던
경로를 없애고 키워드 술어를 페이지 쿼리에 직접 붙였다. 구체 엔진 직접 호출도
해석기 경유로 옮겨 플러그인 검색엔진이 상품·쿠폰·브랜드 검색에 적용된다.

쇼핑 첫 화면과 분류 화면은 각각 5요청·3요청을 한 번으로 합쳤다. 분류 화면이 함께
받던 분류 트리는 그 화면의 어떤 표현식도 읽지 않는 순수 낭비였다. 없는 분류
주소는 빈 화면 대신 404 로 처리해 검색엔진이 정상 페이지로 수집하지 않게 했다.

색인 — 전시 조건으로 좁힌 뒤 등록일·판매가로 정렬하는 목록과 주문 옵션 판매량
집계에 필요한 색인을 더했다. 신규 설치는 마이그레이션이, 기존 사이트는 업그레이드
스텝이 적용한다.

관리자 화면 — 총 건수가 잘린 목록에서 페이저가 접히던 자리와, 쿠폰 사용조건의
상품 검색이 추가 로드되지 않던 조건을 고쳤다. 같은 파일의 사전 결함(반복 렌더 안의
정적 id, Icon 박스 크기, 다크 모드 대비, 반복 변수 선언 오탈자)도 함께 처리했다.
2026-08-06 11:13:51 +09:00
HeuJung 41fe812f2c perf(board): 목록·검색·상세의 중복 실행 제거 및 상한 계약 소비
검색 — 이 코어에 추가한 BOOLEAN MODE 정제기가 게시판에는 도달하지 않아
원문 키워드가 그대로 바인딩되고 있었다. + - * " < > 를 넣으면 오류 화면이 떴다.
코어 정제기 경유로 옮겼고, 같은 술어를 count 와 get 으로 두 번 실행하던 것을
페이지네이터 한 번으로 합쳤다. 구체 엔진 직접 호출도 해석기 경유로 바꿔 플러그인
검색엔진이 게시글·신고 검색에 적용된다.

상세 — 같은 글을 3회, 같은 게시판을 5회 읽고 있었다. 조회한 인스턴스를 넘겨
글 1회·게시판 1회로 줄였고, 조회수 증가를 권한 확인 뒤로 옮겨 열람 권한이 없는
요청이 조회수를 올리지 않게 했다.

목록 — 답변 트리의 무제한 조회에 상한과 순환 가드를 두고, O(n²) 병합을 한 패스로
바꿨다. 댓글도 무제한 조회에 상한을 두되 잘렸다는 사실과 전체 건수를 함께 알린다.
조용히 잘리면 "댓글이 그만큼뿐" 으로 보인다. 원댓글 단위 페이지네이션을 더해
답글이 부모와 떨어지지 않게 했다.

관리자 화면 — 총 건수가 잘린 목록에서 페이저가 "1페이지뿐" 으로 접히던 자리를
고쳤다. 저장소가 상한을 붙인 목록인데 화면이 그 값을 1 로 채우고 있어, 기록이
상한을 넘으면 2페이지에 도달할 방법이 없었다.

같은 파일에서 드러난 사전 결함도 함께 처리했다 — 반복 렌더 안의 정적 HTML id,
Icon 의 박스 크기 지정, 단색 버튼의 다크 모드 대비.
2026-08-06 11:13:51 +09:00
HeuJung 2ae1972a3b perf(core): 대용량 목록 상한 총 건수·커서 계약 신설 및 요청당 반복 실행 비용 정리
공개 이슈 gnuboard/g7 이 지목한 병목은 "같은 일을 반복 실행한다" 축이다.
응답·행의 무게를 다룬 · 과 겹치지 않는다.

공통 계약 — 총 건수 상한과 페이지 이동 범위는 별개 결정이다. 묶으면 필요 없이
기능이 깎인다. 총 건수만 상한을 받고(파생 테이블 COUNT), "다음" 이동은
per_page + 1 실측으로 끝까지 열어 둔다. 계산이 불가능해지는 것은 마지막 페이지
번호 하나뿐이며 그 사실은 last_page: null 이 알린다. 최신순처럼 실제 컬럼으로
정렬하는 목록은 커서로 전환해 깊이와 무관하게 일정 속도로 이동한다. 관련도순은
계산값 정렬이라 커서 키로 쓸 수 없어 offset 을 유지한다.

계약의 입구는 표준 paginate 와 같은 폭이어야 한다. 관계·쿼리 빌더를 받지 못하면
그 좁은 만큼이 그대로 운영 500 이 되고, 실제로 관리자 알림 목록에서 그렇게 터졌다.
응답 조립도 컬렉션마다 손으로 하면 형태가 늘어나는 순간 없는 값을 부르거나 새
필드를 흘리므로, 형태 판정을 paginationMeta 한 곳에 모았다. 표준 paginate
응답은 필드 단위로 이전과 동일하다.

요청당 반복 비용 — 훅 구독마다 남기던 로그 400줄, 요청당 스무 번 넘던 설정 파일
재읽기, 이미 캐시된 목록의 DB 재조회를 없앴다. 권한 판정은 요청 스코프 메모를 두어
화면 요소마다 나가던 조회를 한 번으로 줄였고, 크로스 요청 캐시는 두지 않아 권한
변경이 종전처럼 다음 요청에 반영된다.

검색 질의는 활성 엔진이 만든다. 저장소가 구체 엔진을 지목하면 플러그인이 등록한
엔진은 호출될 기회 자체를 잃고 오류 없이 다른 방식으로 동작한다. 해석기를 두어
활성 엔진에 위임하고, 전문검색이 없는 DBMS 의 부분일치 폴백도 드라이버명 하드코딩
대신 선언형 config 로 옮겼다.

총 건수가 잘린 목록에서 순번을 역산하면 0 과 음수가 나온다. 지어내지 않고 null 을
돌려주며, 그 원칙을 last_page 와 동일하게 적용했다.
2026-08-06 11:13:51 +09:00
eunhyeandGitHub 97ab99b7cd Merge pull request from gnuboard/chym1217/issue529
feat(message_bizppurio): 인증 토큰 캐시 정합성 및 연결 확인 기능 추가
2026-08-06 10:55:47 +09:00
chym1217 f9695df74a feat(message_bizppurio): 인증 토큰 캐시 정합성 및 연결 확인 기능 추가
설정 저장 시 토큰 캐시를 무효화하여 비밀번호 변경 후 최대 23시간 옛
자격증명이 재사용되던 문제를 해소하고, 저장된 계정/비밀번호를 그
자리에서 검증하는 "연결 확인" 버튼을 추가한다. 비밀번호 필드 라벨을
G7 로그인 비밀번호와 구분되도록 정정하고, 토큰 발급 실패 시 비즈뿌리오
원문 사유를 노출해 원인 추적을 돕는다.
2026-08-06 10:51:14 +09:00
정정홍andGitHub 1de67731b7 Merge pull request from gnuboard:HeuJung/issue518
HeuJung/issue518
2026-08-06 09:02:32 +09:00
HeuJung 4f6fced2db fix(admin_basic): 레이아웃 코드 편집 화면의 패널 폭·목록 갱신·버전 이력 도달 범위
리베이스 결과를 브라우저로 검수하다 같은 화면에서 셋을 확인했다. 셋 다 응답에도
콘솔에도 신호가 없고 화면에서만 드러나는 종류다.

- 두 카드 어디에도 폭 규약이 없어 flex 가 max-content 비율로 폭을 나눴다. 모듈
 파티션 레이아웃의 공백 없는 긴 파일명이 목록의 max-content 를 밀어 올려 편집기가
 절반 이하로 눌렸다(실측 목록 814 / 편집기 473 → 384 / 903). 이름은 자르지 않고
 접는다 — 잘라내면 어느 파티션인지 구분할 수 없다.
- 저장 뒤 상세만 다시 불러서, 방금 저장한 파일의 목록 행이 저장 전 설명·크기·수정일을
 그대로 달고 있었다. 목록 응답이 본문을 싣지 않게 된 뒤로 재조회가 수십 KB 라
 매 저장마다 부른다.
- 서버가 버전 목록에 기본 상한 100 을 도입했는데 이 화면에는 limit 도 '더 보기' 도
 없어 상한 밖 버전이 도달 불가였다(실측 114건 중 14건). 페이징이 위지윅 편집기에만
 들어가 상한 도입이 이 화면에서는 그대로 기능 축소가 됐다.

회귀는 레이아웃 구조(5건)와 브라우저(6건) 양쪽에서 잠근다. 브라우저 쪽은 수정을
되돌린 상태로 fail 을 먼저 확인했다.
2026-08-06 09:02:00 +09:00
HeuJung b5c0ec1178 fix(core,ecommerce,board,page,admin_basic,basic): 목록 응답의 하위 컬렉션 전량 직렬화 해소
관리자 상품목록을 한 페이지 여는 것만으로 그 페이지 모든 상품의 옵션이 응답에 실렸다.
같은 패턴을 저장소 전역에서 찾아 14개 목록 엔드포인트를 함께 정리했다.

근본 원인은 둘이다. Resource 가 whenLoaded 로 방어하는데 Repository 가 목록 쿼리에서
관계를 무조건 로드해 가드가 항상 참이 되는 가짜 가드, 그리고 toListArray 경량 표현을
정의해 두고도 컬렉션이 toArray 를 부르는 목록/상세 미분리다. 둘 다 응답만 보면
정상이라 오류도 경고도 없이 페이로드만 불어난다.

목록은 화면이 실제로 그리는 것만 싣는다. 개수·합계는 PHP 컬렉션 연산이 아니라 DB
집계로, 대표 1건이 필요한 곳은 관계 자체를 oldestOfMany 로 좁힌다. eager load 의
limit(1) 은 부모별이 아니라 배치 쿼리 전체에 걸려 첫 행만 값을 갖게 되므로 쓸 수 없다.

뺀 값에는 대체 경로를 먼저 만들었다. 상품 옵션은 행을 펼칠 때 배치로 불러오고(상품 수와
무관하게 쿼리 상수), 종전 동작이 필요한 호출자를 위해 ?with_options=1 등 opt-in 을 남겼다.
배송정책 국가설정과 리뷰 첨부 이미지는 소비처를 실측한 결과 화면이 실제로 그리고 있어
제거하지 않았다 — 그 소비 사실을 회귀 테스트로 고정했다.

재발 방지로 정적 검사 룰 4종과 규정 문서 항목을 함께 넣었다.
2026-08-05 21:20:05 +09:00
정정홍andGitHub f5b411d9a0 Merge pull request from gnuboard:HeuJung/issue527
fix(security): 예약 작업·언어팩·인스톨러의 차단목록을 허용목록으로 전환
2026-08-04 17:50:23 +09:00
HeuJung 4b6adf4788 fix(security): 예약 작업·언어팩·인스톨러의 차단목록을 허용목록으로 전환
KISA 신고포상제 접수 3건(KVE-2026-1677/1678/1679)의 공통 원인은 하나다 —
"위험한 것을 골라 막았고, 목록 밖으로 우회됐다". 세 지점 모두 판정을 뒤집는다.

예약 작업 Artisan (KVE-2026-1679)
차단 9개 / 허용 229개였던 구조를 허용목록으로 바꾸고 명령마다 허용 옵션을 선언한다.
검증 과정에서 보고서보다 짧은 경로를 추가로 발견했다 — 검증기는 preg_split 의 첫
토큰을 명령명으로 봤지만 실행부는 문자열째 넘겼고, parameters 가 비면 Laravel 이
StringInput 으로 재파싱한다. 따옴표 한 쌍이면 차단목록의 tinker 가 그대로 실행됐다
(마커 파일로 확인). 파서를 엄격 형태 하나로 고정하고 실행은 (명령명, 인자배열)로 넘겨
재해석 지점 자체를 없앴다.

언어팩 (KVE-2026-1678)
위험 함수 11개를 정규식으로 나열하던 검사를 토큰 상태기계로 교체해 "번역 배열만"
통과시킨다. 목록에 없는 함수도, 함수 이름을 아예 쓰지 않는 형태도 원리적으로 막힌다.
파일 형식·PHP 위치·심볼릭 링크도 화이트리스트로 뒤집었다. 활성 승격이 require 배선의
필수 조건이므로 설치와 활성화의 권한 경계를 네 설치 경로 전부에 같은 강도로 건다 —
경로별 면제는 곧 우회로가 되기 때문이다. 화면은 운영자의 활성화 권한을 보고
auto_activate 를 실을지 결정하므로 재설치 흐름은 그대로 동작한다.

인스톨러 (KVE-2026-1677)
실행 바이너리 자리에 인자가 들어가는 것을 형태 규칙으로 차단한다. escapeshellarg 는
"하나의 인자" 임만 보장할 뿐 그 인자가 실행 파일인지 스크립트인지는 보장하지 않는다.
판정을 의존성 0 공용 정책으로 모아 진단 API·설치 워커·저장 시점이 같은 규칙을 쓴다.
stat 은 호출하지 않는다 — 접근이 제한된 서버에서 정상 경로가 거부되던 회귀를
재발시키지 않기 위함이다.

검증: 백엔드 392건 + 프론트 7건 green(skip 1, symlink 미지원 환경). PoC 마커로 red 를
먼저 확보하고, 수정 후 stash 역회귀로 다시 red 가 되는 것까지 확인했다.
2026-08-04 17:48:05 +09:00
정정홍andGitHub 30867e0e0b Merge pull request from gnuboard:HeuJung/issue503
HeuJung/issue503
2026-08-04 10:42:20 +09:00
HeuJung 1ce434e2da fix(ecommerce): 비회원 주문 상세의 상품별 배송정책 표시 누락 해소
비회원 주문 조회 상세는 회원 상세와 같은 화면 구성(partial)을 쓰는데
비회원 응답에만 배송정책 스냅샷이 없어 그 줄만 조용히 비어 있었다.
오류도 빈 값 표기도 없이 항목 자체가 사라지므로 응답을 열어 보기 전에는
드러나지 않는다.

스냅샷 전체를 그대로 내보내면 원가·계산 근거까지 비회원에게 노출되므로
ShippingPolicySnapshot::forDisplay 로 화면이 쓰는 정책명·개별 배송비만
추려 내보낸다. 내부 계산 정보는 종전대로 제공하지 않는다.
2026-08-04 10:34:00 +09:00
HeuJung e426498fb3 fix(core): 레이아웃 코드 편집 화면 설명의 미해석 표기 노출 해소
파일 목록과 선택한 파일의 설명에 `$t:…` / `{{ … }}` 가 그대로 보였다.
설명 원문은 소유 템플릿의 다국어 키인데 응답 조립 시점에는 요청 템플릿의
사전만 열려 있어 다른 템플릿의 레이아웃을 편집하면 키를 찾지 못했다.
번역 실패가 예외 없이 원문 노출로 이어져 화면에만 나타났다.

목록·상세가 같은 규칙을 쓰도록 표시 해석을 App\Support\LayoutDescription 으로
모으고, 컨트롤러가 소유 템플릿 사전을 주입한다. 표현식이 섞인 설명과 번역을
찾지 못한 경우는 파일 이름으로 대체한다.

목록만 고치면 상세 헤더에 그대로 남는다 — 브라우저 실측에서 목록 수정 후에도
헤더가 새던 것을 확인해 상세 경로(LayoutResource)까지 함께 처리했다.
2026-08-04 10:21:00 +09:00
HeuJung 65378fdeb6 fix(engine): 반복 렌더 경로의 리터럴 단일 바인딩 판정 누락 해소
판정 통일(engine-v1.55.0)에서 리터럴 해석을 BindingShape 한 곳으로 모았으나
renderItemChildren 만 `hasPipes → isComplexExpression → 경로 탐색` 3분기를 직접
갈라 리터럴을 몰랐다. 그 결과 같은 `{{true}}` 가 조건 자리에서는 true,
목록 셀·카드 등 반복 렌더 prop 자리에서는 undefined 로 갈렸다 —
통일이 없애려던 비대칭이 이 지점에만 남아 있었다.

형태 판정을 resolveSingleBindingValue 에 위임해 다른 경로와 같은 규칙을 쓴다.
같은 지점이 빈 바인딩(`{{}}`)도 몰라 경로 탐색으로 보내던 것을 함께 봉인했다.

브라우저 실측(배포 번들의 G7Core.renderItemChildren 직접 호출)으로 발견했고,
저장소 레이아웃의 리터럴 단일 바인딩 60건은 전부 if/condition 자리라
화면 표시에는 변화가 없다.

SEO 패리티: 봇 측 ExpressionEvaluator 는 이미 리터럴을 해석하고 있었으므로
이 수정은 React 경로를 봇 경로에 맞춘 것이다 (파일 헤더에 면제 사유 기재).
2026-08-04 10:08:00 +09:00
HeuJung 60cccbf62d feat(ecommerce): 마일리지 적립 절사 기준 신설 및 주문 시점 스냅샷
적립 포인트 절사가 코드에 1점 버림으로 고정돼 있어 운영자가 조정할 수 없었다.
언어/통화 설정의 환율 절사를 따라가는 방안을 먼저 검토했으나, 그쪽은 외화 환산
분기에서만 적용되어 기본 통화 상점에는 따라갈 값 자체가 없다. 적립은 기본 통화
원장에 정수로 확정 기록되는 값이라 성격이 다르므로 마일리지 통화별 규칙에 신설했다.

- earn_rounding_unit(1/10/100) + earn_rounding_method(floor/round/ceil)
- 기본값은 종전 하드코딩과 정확히 같은 금액을 산출 — 기존 쇼핑몰 적립액 무변화
- 주문 계산·옵션 분할·부분취소 안분 전 경로에 동일 기준 적용
- 주문 시점 값을 스냅샷으로 보관 — 이후 기준을 바꿔도 과거 주문이 소급 변경되지 않는다
- 설정 UI·검증·다국어(ko/en/ja)·API 문서 동반, 기존 사이트는 업그레이드 스텝이 명시 기입

함께 처리: 마일리지 내역 목록 테스트가 첫 컬럼 field 를 `no` 로 단언해 실패하던 것을
현재 레이아웃 값(`number`)에 맞춰 정정했다. 이 브랜치와 무관한 stale 케이스다.
2026-08-04 09:56:00 +09:00
HeuJung 0ae3289c60 fix(ecommerce,basic): 배송정책 스냅샷 형태 정규화로 주문 상세 배송정보 누락 해소
마이페이지·비회원 주문 상세에서 상품별 배송정책명과 개별 배송비 줄이 아무 안내
없이 통째로 사라졌다. 주문에 보관하는 배송정책 스냅샷이 항목 목록과 배송지 메타를
한 배열에 섞어 담고 있어, PHP 배열이 non-sequential 이 되면서 json_encode 가 리스트가
아니라 객체로 직렬화했기 때문이다.

서버 소비자는 정수 키 판별로 관용 처리해 무사했고 배열을 전제한 화면만 죽었다.
한 컬럼에 두 의미를 담은 구조 자체를 items + address 로 분리했다.

이미 저장된 주문은 화면만 고쳐서는 낫지 않으므로 업그레이드 스텝으로 함께 정규화한다.
2026-08-04 09:39:00 +09:00
HeuJung 19789433df perf(core): 레이아웃 목록 응답에서 편집 대상 아닌 본문 제외
레이아웃 코드 편집 화면이 목록을 부를 때 모든 레이아웃의 본문(content)까지 함께
내려받고 있었다. 레이아웃이 많은 템플릿에서는 목록을 여는 것만으로 수십 MB 가
브라우저에 쌓여 화면이 크게 느려졌고, 디버그 모드에서는 탭이 메모리 부족으로
종료됐다. 목록 화면은 본문을 쓰지 않고 선택한 파일 하나만 편집한다.

목록 전용 Resource 를 두어 이름·설명·크기·수정일만 싣고, 본문은 상세 조회에서만
읽는다. 크기는 목록에서 표시하므로 저장소가 길이만 계산해 돌려준다.
2026-08-04 09:22:00 +09:00
HeuJung 953616552f fix(core,engine,ecommerce,admin_basic): 렌더 경로 판정·파서 단일화로 표현식 비대칭 일괄 해소
표현식 해석 로직이 엔진 전역에 복제되면서 복사본마다 기능 집합과 판정 기준이
갈라졌고, 같은 작성이 어느 렌더 경로를 타느냐에 따라 다른 결과를 냈다. 증상
대부분이 예외가 아니라 조용한 오답 또는 값 소실이라 재현 위치를 특정하기 어려웠다.

판정만 BindingShape 단일 출처로 모으고 실행은 지점별로 유지했다. 전면 통합은
회귀 위험이 크고, 갈라진 것은 실행이 아니라 판정이었기 때문이다.

- 파이프 평가를 문자열 보간에 위임하면서 생긴 비대칭 3건 해소 (중괄호 노출 /
 _computed stale 전파 / 원본 타입 소실)
- 파이프 분기 누락 8지점 보강 — 평가 지점 모집단을 스캔해 가드를 강제하는 회귀선 신설
- 단일 바인딩·복잡식 판정 14곳을 정본으로 치환, 빈 바인딩 봉인
- BINDING_PATTERN 정규식을 수동 스캐너로 교체, resolveObject 를 key 단위 예외 격리로 전환
- iteration + 항목 참조 if 가 목록 전체를 지우던 결함 해소, 반복 경로 기능 격차 정렬
- 템플릿 컴포넌트의 new Function 자체 평가기 제거 → G7Core.evaluateCondition 위임
- 컨텍스트에 식별자가 아닌 키가 하나라도 있으면 그 컨텍스트의 모든 식이 평가되지
 못하던 결함 수정
- SEO 봇 렌더러도 같은 규칙으로 정렬 — React 만 고치면 봇 화면에서만 목록이 빈다
- audit walker 가 레이아웃의 74%(partial 루트 444/598)로 내려가지 못하던 사각 해소,
 신규 룰 5종 등재
2026-08-04 09:05:00 +09:00
정정홍andGitHub ca4fd02e99 Merge pull request from gnuboard:HeuJung/issue506
HeuJung/issue506
2026-08-02 19:17:04 +09:00
HeuJung 6bde24bc75 fix(ecommerce): 숫자 설정 문자열 영속으로 인한 주문 생성 실패 해소
관리자 환경설정에서 미입금 자동취소 기한을 저장하면 HTML number 입력의
문자열이 그대로 영속되고, Carbon 3.13 의 strict 타입 경계에서 TypeError 가
발생해 무통장입금·가상계좌 주문이 전면 실패했다(공개 제보). 한 곳만 막으면
다른 저장 경로가 남으므로 저장·조회·소비·모델 네 계층에 각각 방어를 두고,
저장소 전역에서 같은 형태가 더 없음을 audit 룰로 확정했다.

같은 계획서 수행 중 드러난 인접 결함도 함께 해소했다.
- 쿠폰 수정 경로로 (발급일 기준, 일수 NULL) 조합이 저장되어 발급 즉시
 만료된 쿠폰이 조용히 나가던 문제 — 저장·발급 양쪽에서 차단
- 리뷰 작성 기한을 화면과 저장 판정이 서로 다른 기준으로 계산하던 문제
- 결제 플러그인이 PaymentMethodEnum 과 다른 어휘로 지원 결제수단을 선언해
 가상계좌·계좌이체·휴대폰결제에 PG사를 지정할 수 없던 문제

엔진에서는 `_localInit` 적용 여부를 전역 해시로만 판정해 인스턴스별 저장소가
리셋되지 않던 결함(engine-v1.54.5)과, 제거 대상이 없어도 상태 갱신·캐시
무효화가 상시 발생하던 판정 결함(engine-v1.54.6)을 고쳤다.

Playwright MCP 정밀 점검(T1~T11)에서 설정이 빈 문자열로 남은 사이트의
주문서 입금기한 안내가 미치환 플레이스홀더를 노출하는 것을 실측해, 화면
폴백을 서버와 같은 규칙으로 맞췄다.
2026-08-02 18:35:28 +09:00
정정홍andGitHub ef2a77e0a0 Merge pull request from gnuboard:HeuJung/issue492
HeuJung/issue492
2026-08-02 16:48:52 +09:00
HeuJung 9025f6d1aa fix(core,ecommerce,gdpr,message_bizppurio,hello_module,basic,admin_basic): 리베이스 후속 정합 정리
develop 리베이스 3커밋이 각자 정상인데 합쳐지면서 어긋난 지점들을 정리한다.

- 발송 이력 목록이 뒤 페이지로 갈수록 느려지고 같은 시각 발송분이 중복
 노출되던 두 결함을 코어 지연 조인으로 함께 닫았다. 이 파일은 develop 쪽에서
 들어와 492 브랜치에서는 검출될 수 없었다.
- 정렬 허용 컬럼·지연 조인 호출 계약 테스트가 순수 소스 스캔인데도 앱을 부팅해
 케이스마다 DB 연결을 잡았다. 스캔 대상이 늘자 연결 상한(151)을 넘겨 실패했고,
 앱 의존이 없으므로 부팅을 뺐다.
- 확장 업데이트 모달 테스트는 stub 이 모든 체크박스에 같은 식별자를 붙여,
 검색 인덱스 재생성 체크박스가 추가되자 대상을 특정하지 못했다.
- 동의 이력 필터 어휘가 enum 전 케이스로 넓어진 것을 테스트 기대값에 반영하고,
 이력 페이지네이션 버튼의 액션 구조를 나머지와 같은 형태로 통일했다.
- 관리자 확장 화면의 존재 확인 가드가 추가된 뒤 갱신되지 않은 mock 을 맞췄다.

다크 모드 대비가 빠진 버튼 둘을 저장소 관례에 맞추고, 사용자 템플릿에서
그 클래스가 실제 CSS 로 생성되도록 safelist 를 함께 채웠다.
2026-08-02 16:42:52 +09:00
HeuJung b10e89bef0 fix(core,board,ecommerce,page,gdpr,ckeditor5,kginicis,admin_basic,basic): 목록 조회·검증 계층 정비 및 감사 지적 전건 처리
목록 조회 성능 축(깊은 OFFSET·정렬·색인)과 그 검증 계층에서 계획서 전수검수와 3회에 걸친
감사가 지적한 항목을 처리했다. 세부 경위는 의 각 회차 문서에 있다.

검증 계층 — 컨트롤러가 base Request 를 직접 주입받던 확장 13곳을 전용 FormRequest 로 옮겼다.
옮기면서 기존 동작 계약은 그대로 두었다: 상한 초과 limit 을 거부하지 않고 상한까지 반환하던
공개 API, per_page 를 범위로 조정하던 관리자 목록, 미지원 period 를 year 로 해석하던 인기글
목록 모두 종전과 같은 응답을 낸다. 상한/폴백을 rules 로 승격시키면 200 이던 응답이 422 가
되어 기존 링크가 깨지므로, 규칙은 타입만 닫고 클램프·폴백은 접근자가 맡는다. period 는
접근자가 닫힌 집합만 반환해 캐시 키 공간도 함께 닫힌다.

ckeditor5 이미지 업로드만 ResponseHelper 봉투를 쓰지 않는다. 응답을 파싱하는 주체가 CDN 으로
로드되는 상위 CKEditor5 43.3.1 의 SimpleUploadAdapter 라 규약을 바꿀 수 없어, 각 응답 지점에
사유를 명시한 면제를 부착하고 근거를 API 문서에 남겼다.

하네스 — 룰 5개의 대상 경로 패턴이 매처와 맞지 않아 번들 확장 컨트롤러가 검사 대상에서
통째로 빠져 있었다. 패턴을 고치자 확장 위반 17건이 드러나 전건 처리했다. severity 오타가
요약 집계 양쪽에 안 잡혀 "0 error" 로 보고되던 문제도 런너 사전 검증으로 막았다.

정렬 게이트 대조 하네스는 관계 정렬 변형만 쓰는 저장소를 탐지하지 못한 채 통과시키고 있었다.
탐지·제외·인자 파싱을 함께 고치고 단위 테스트를 신설했다.
2026-08-02 15:29:29 +09:00
HeuJung 5f4a733b3b feat(core,board,ecommerce,page): 성능 계측 인프라 4축 확장 및 계측 대상 선언 지점 신설
계측 축이 "목록 SELECT" 하나였고 프로파일 12종이 커맨드 파일에 하드코딩돼
있어 확장이 자기 대상을 추가할 수 없었다. 확장이 설치·제거되는 설치본마다
"실제로 존재하는 대상"이 달라지므로, 대상을 소유자가 선언하고 코어가 수집하도록
뒤집었다.

- 선언 지점 두 곳, 스키마 하나: 코어 config/benchmark.php,
 확장 getBenchmarkProfiles (모듈/플러그인). 수집은 method_exists 기반이라
 인터페이스를 직접 구현한 서드파티 확장이 깨지지 않는다.
- 축 4종(목록/화면/쓰기/배치)을 실행기로 분리하고 컨테이너 태그로 주입 —
 축이 늘 때 커맨드를 고치지 않는다.
- 화면 축은 HTTP 커널로 내부 요청을 처리한다. 라우트만 dispatch 하면 전역
 미들웨어를 건너뛰어 화면과 다른 것을 재게 된다. 인증은 프로파일이 선언한
 권한만 가진 임시 계정이 기본이며 --as 로 기존 계정을 지목할 수 있다.
- 화면/쓰기 축은 계측 계정 생성부터 처리까지 롤백되는 트랜잭션 안에서 실행해
 운영 DB 에도 잔여 데이터를 남기지 않는다. 배치 축은 내부 커밋·락 보유 때문에
 감싸지 않고 --allow-write 로만 통제한다.
- 잘못된 선언은 조용히 버리지 않고 사유를 경고로 남긴다. 버려진 선언이 곧
 계측 사각이 되기 때문이다. 전부 건너뛴 실행은 종료 코드 1 로 끝난다.
- 리포트는 환경 정보를 함께 적어 저장소 밖(storage/app/benchmarks)에 남긴다.
 측정값이 실행 머신에 종속되므로 저장소에 축적하면 비교 불가능한 수치가 섞인다.

작업 중 발견해 함께 고친 것:

- 이관 프로파일 3종의 soft_delete 선언이 실제 스키마와 어긋나 있었다
 (users/product_inquiries/pages 에 deleted_at 없음).
- 주문 목록이 상태 미지정 시 임시 주문 상태를 NOT IN 으로 제외하는데 선언에
 빠져 있었다. filters 가 등가 비교만 지원한 것이 근본 원인이라 연산자 형태를
 받도록 넓히고, 미허용 연산자는 측정 전에 거부한다.
- 댓글 프로파일은 지배적 술어가 상관 서브쿼리라 선언형으로 재현할 수 없어
 제거했다. 맨 테이블 스캔은 어느 화면도 내지 않는 수치가 된다.

g7:bench:pagination → g7:bench 로 개명(4축을 재므로). 이전 이름은 별칭으로 남겼다.

테스트 61건 통과, 라이브 18종 전건 측정.
2026-08-02 14:44:33 +09:00
HeuJung be9befe023 fix(core,board,ecommerce,page,gdpr,basic): 깊은 OFFSET 목록 조회 개선 및 raw/테이블명 전수 정리
목록이 뒤쪽 페이지로 갈수록 느려지는 원인은 건너뛰는 행의 넓은 컬럼까지 읽는 것이었다.
지연 조인(inner 는 키 컬럼만, outer 는 이번 페이지 행만 전체 컬럼)과 정렬 컬럼 해석을
코어 공통 트레이트로 두고 코어 8 + 확장 6 저장소에 적용했다.

감사 지적 처리:
- 그룹 쿼리의 총 건수 오산 — count 는 groupBy 를 벗기지 않아 첫 그룹의 행 수를
 총 건수로 돌려준다. getCountForPagination 으로 교정.
- audit 전수 스캔이 버퍼 한계로 한 번도 동작하지 않던 결함과, 확장 경로를 놓치던
 정규식 5건을 함께 고쳤다. 그동안 번들 확장의 위반이 보이지 않았다.

raw 20건을 빌더 표현으로 치환하고 하드코딩 테이블명 16건을 모델 유래로 바꿨다.
이 과정에서 마이페이지 「내가 댓글 단 글」 검색이 조인 별칭 충돌로 SQL 오류를 내던
결함이 해소됐다. 재발 차단용 룰 2종을 신설하고, 컬럼 프루닝 룰을 error 로 승격했다
(전역 위반 0건 — warning 으로 두면 "0 error" 판정을 통과하며 무음으로 재누적된다).

정렬 허용 목록은 FormRequest 게이트를 덮어야 한다. 좁으면 검증을 통과한 정렬이 422 도
로그도 없이 기본값으로 되돌아간다(스케줄 실행 이력의 종료시각·소요시간 정렬이 그랬다).
호출처를 스캔해 매핑 완전성까지 검사하는 계약 테스트로 전수 강제한다.

해외 배송지 결함도 함께 고쳤다 — 조회는 city/state/postal_code 로 내려주고 폼은
intl_* 로 제출하는 비대칭 때문에, 수정 시 입력칸이 비어 보이고 저장하면 기존 값이
유실됐다. 쿠폰 수정의 유효기간 미검증과 1인당 사용 제한 부분 수정 차단도 함께 수정.
2026-08-02 14:36:37 +09:00
정정홍andGitHub 95ffdf9eed Merge pull request from gnuboard:HeuJung/issue495
fix(core,engine): OFFSET 순회 중 필터 컬럼 변경으로 인한 처리 누락 수정
2026-07-31 22:19:37 +09:00
HeuJung 1eedbbdda1 fix(core,engine): OFFSET 순회 중 필터 컬럼 변경으로 인한 처리 누락 수정
Laravel chunk/each 는 OFFSET 기반이라, 콜백이 행의 필터 소속을 바꾸면
(필터 컬럼을 채우거나 매칭 행을 삭제하면) 다음 조회가 그만큼 앞으로 밀려
미처리 행을 조용히 건너뛴다. 250건/청크 100 이면 정확히 100건이 남는다.
코어·번들 확장 전수 조사로 확정한 6곳을 chunkById(키셋 순회)로 통일했다.

브라우저 검수(28축)에서 계획에 없던 결함 12건이 드러나 같은 사슬에서
수정했다. 이 중 layout_strategy=keep 무효화와 수정 감지 실패 시 "수정 없음"
오보는 사용자 작업 소실에 직결된다.

재발 방지로 audit 룰을 추가했고, 수정 6곳을 원복해 red 6건 / 복원 후 green
0건으로 판별력을 검증했다.
2026-07-31 21:50:45 +09:00
정정홍andGitHub 1866f0d566 Merge pull request from gnuboard:HeuJung/issue276
HeuJung/issue276
2026-07-31 08:12:50 +09:00
HeuJung ea9a14725b docs: 리베이스 후 확장 API 레퍼런스 인덱스 재생성
develop 이 추가한 확장 1건이 ·AGENTS.md 의 자동 생성 표에서 빠져 있었다.
리베이스 시 두 파일이 본문 기준으로 병합되면서 생성 섹션만 낡은 상태로 남은 것이라,
generate-docs-index.cjs 로 재생성해 12개 → 13개로 맞춘다.
2026-07-31 07:27:48 +09:00
HeuJung 0f0ef8fa8d test(e2e): 편집기 저장 spec 전용 시드 화면 분리 + 실패 10건 수정
레이아웃 편집기 spec 중 저장(PUT)까지 수행하는 것들이 제품 화면에 편집 결과를
영속시켜, 실행할 때마다 개발 사이트에 노드가 누적됐다(home 에 빈 표 7개,
관리자 대시보드에 빈 차트 5개). spec 안에 "추가한 노드 삭제 후 재저장" 원복을
넣어도 소용이 없었다 — 원복 후에도 레이아웃이 오염 시점과 정확히 같은 크기로
되돌아왔다. 원복에 기대는 대신 저장 대상 자체를 E2E 전용 시드 화면으로 분리한다.

시드 화면은 활성 템플릿 디렉토리에만 설치되므로 배포 원본과 릴리스 산출물은
변하지 않는다. globalSetup 이 매 실행 fixture 원본으로 덮어써 회차 간 누적이
성립하지 않으므로 저장 spec 에 원복 절차가 필요 없다. 설치는 레이아웃 파일과
라우트, DB 행 세 가지를 함께 다루되 시드 행만 건드린다 — 전체 재동기화를 쓰면
편집기로 저장한(파일에 없는) 변경이 실행할 때마다 사라지기 때문이다.

전수 실행에서 드러난 기존 실패도 함께 처리했다. 35건 중 27건은 절전·부하
아티팩트였고 나머지는 원인을 확정해 고쳤다. 템플릿 식별자 자리에 모듈 id 를
넣어 라우트 트리가 비어 있었고, "캔버스 텍스트가 길어진다"는 대리 지표가 렌더
여부와 무관했으며(정상 렌더에도 짧아진다), 팔레트 삽입 위치가 잠금 서브트리
안이면 선택이 조상으로 밀려 노드를 지목할 수 없었고, 상태 개수 단언이 상태
추가로 낡았고, if 게이트상 그 탭에 등록되지 않는 소스를 refetch 하고 있었고,
무통신 구간이 오지 않는 관리자 화면에 networkidle 을 기다렸고, 테스트 종료 후
도착한 route 콜백이 워커를 중단시켰고, 같은 설정 화면에 저장하는 테스트들이
병렬로 충돌했다.

측정 방법에서 반복해 틀린 지점들은 E2E 가이드에 표로 남겼다 — 가림은 가시성
판정으로 잡히지 않고, 길이 증감은 렌더 신호가 아니며, 총 개수 단언은 스펙이
늘면 깨지고, networkidle 은 실시간 연결이 있는 화면에서 오지 않는다.
2026-07-31 00:43:58 +09:00
HeuJung 6b3903553a fix(e2e,a11y): 확장 E2E 로케일 고정 + 관리자 탭·스위치 접근성 보완
확장 E2E config 의 로케일 미고정을 코어 포함 7개에서 해소했다. 미지정 시 첫 페이지
로드의 Accept-Language 가 en-US 로 나가고 엔진이 그 값을 localStorage 에 굳혀,
인증 후에도 세션 전체가 영어로 렌더돼 한국어 문구를 단언하는 spec 이 전멸한다.
같은 함정을 정적으로 막는 룰을 함께 등록했다.

관리자 공용 탭·스위치는 ARIA 와 키보드 처리가 없어 마우스 없이는 조작할 수 없었고
보조기기에 목록·선택 상태가 전달되지 않았다. WAI-ARIA 규약대로 보완하고, role 변경에
맞춰 낡은 셀렉터를 갱신했다(admin_basic E2E 회귀 1건 포함).

nhnkcp 설정 화면은 저장 후 정규화 결과가 입력칸에 반영되지 않았다. 계측 결과
refetch 와 initLocal 은 정상 동작하며 _local 도 갱신되지만 사용자가 편집한 입력칸만
값을 유지한다. 저장 응답으로 폼을 명시 재바인딩하고 그 기전을 트러블슈팅에 남겼다.

safelist 생성기가 빌드마다 main.css 에 빈 줄을 누적해 매 빌드가 소스를 변경된 것으로
만들던 문제도 함께 고쳤다.
2026-07-31 00:43:56 +09:00