표현식 해석 로직이 엔진 전역에 복제되면서 복사본마다 기능 집합과 판정 기준이
갈라졌고, 같은 작성이 어느 렌더 경로를 타느냐에 따라 다른 결과를 냈다. 증상
대부분이 예외가 아니라 조용한 오답 또는 값 소실이라 재현 위치를 특정하기 어려웠다.
판정만 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종 등재
Laravel chunk/each 는 OFFSET 기반이라, 콜백이 행의 필터 소속을 바꾸면
(필터 컬럼을 채우거나 매칭 행을 삭제하면) 다음 조회가 그만큼 앞으로 밀려
미처리 행을 조용히 건너뛴다. 250건/청크 100 이면 정확히 100건이 남는다.
코어·번들 확장 전수 조사로 확정한 6곳을 chunkById(키셋 순회)로 통일했다.
브라우저 검수(28축)에서 계획에 없던 결함 12건이 드러나 같은 사슬에서
수정했다. 이 중 layout_strategy=keep 무효화와 수정 감지 실패 시 "수정 없음"
오보는 사용자 작업 소실에 직결된다.
재발 방지로 audit 룰을 추가했고, 수정 6곳을 원복해 red 6건 / 복원 후 green
0건으로 판별력을 검증했다.
일반 화면(React)과 봇 화면(PHP)은 같은 레이아웃을 각각 렌더하는데 봇
화면 쪽 지원 범위가 뒤처져, 확장이 교체한 본문이 통째로 빈 채 나갔다
(공개 이슈 ). 같은 결손 계열을 전수 조사해 함께 닫는다.
- 확장 주입 props 를 렌더 진입 시점에 해석해 자손 전체에 상속
→ 게시글·답글 원글·댓글·페이지·상품 설명 5개 지점 복구
- 사용자 작성 HTML 정화기 신설 — isHtml 판정에 따라 이스케이프/정화.
미정화 상태에서 봇 경로로 저장형 스크립트가 실제 실행됐다
- 노드 키 패리티: conditions / type:iterator / responsive 매칭 규칙 /
children 문자열 / text 우선순위 / $t:defer / 레이아웃 초기 상태
- 표현식: 단일 바인딩 판정 교체, ||·&& 를 값 반환(JS 시맨틱)으로,
비교식이 raw 경로에서 null 이 되던 문제
- 데이터소스: 엔드포인트 표현식 전면 해석 + 레이아웃 8곳 선언 보강
- 봇 페이지 캐시 1회 전량 무효화 업그레이드 스텝
지원 범위 SSoT 표를 문서에 고정하고, 한쪽만 고치는 것을 막는 검사 룰과
레이아웃 전수 순회 테스트로 잠갔다.
계획서 위임 항목(Q6·Q7·Q8) 마감.
Q8: sitemaps.org 폐쇄 어휘를 App\Enums\SitemapChangeFreq 로 도입해
저장(SitemapIndexer)·렌더(SitemapXmlRenderer) 두 경계에서 normalize 하고,
생산 지점(생성기 정적 URL·3 contributor·4 리스너) 리터럴을 enum case 로 치환.
규격에 없는 changefreq 가 사이트맵에 그대로 출력되던 것을 차단(대소문자·공백 흡수).
Q6: Upgrade_7_0_0_beta_3Test 의 "동결 TARGETS === 살아있는 config" 단언은
동결 규정과 충돌하는 무효 불변식(config 는 에서 정당하게 진화)이므로
의도적 divergence 를 검증하도록 재작성.
Q7: 코어 20건 사전 실패를 격리 실측으로 재분류 — 6클러스터는 전 스위트
OOM/싱글톤 누수 오염(격리 시 통과), GenericNotificationViaTest 는 readiness
게이트를 mock 하지 않아 빈 settings 로 mail 이 항상 제외되던 stale test 를 정정.
관리자 "전체 재생성"을 큐(Full 모드)로 던지고 진행상황을 SEO 탭에서 확인한다.
Reverb ON 이면 방송 채널 구독으로 실시간 갱신, OFF 면 상태 API 폴링으로 폴백한다.
- SitemapProgress 진행상황 스토어(queued→running→writing→completed/failed,
캐시 TTL 3600, 방송 스로틀) + SitemapManager.getStatus 확장(progress + realtime_enabled)
- GET /api/admin/seo/sitemap/status(core.settings.read) 신규
- 관리자 재생성을 항상 Full 모드로 큐잉 + 진행률 기록, Job 최종 실패 시 progress=failed 고정
- core.admin.seo.sitemap 방송 채널 인증(core.settings.read)
- SEO 탭 진행상황 UI(상태 배지/단계/누적 URL/실시간·폴링 안내) + 폴링 분기
- 재생성 중 누적 URL 표기(replaceAllForContributor 삽입 건수 반환 — count 쿼리 없이 스트림 누적)
- ja 언어팩 진행상황 UI 번역 동기화
사이트맵을 리소스 단위로 영속화(sitemap_urls)해 진정한 증분 갱신과
도메인 재쿼리 없는 파일 재작성을 구현했다. 리소스 공개/비공개/삭제 시
해당 항목만 색인이 갱신되고, 관리자=full·스케줄러=auto·리소스 변경=증분으로 동작한다.
- sitemap_urls 마이그레이션 + SitemapUrl 모델. loc(2048) 를 unique 에 직접 넣으면
InnoDB 키 길이를 초과하므로 loc_hash(sha256) 컬럼을 identity 로 사용
- SitemapUrlRepository(멱등 upsert / remove / 스트리밍) + SitemapIndexer
(+ 제3자용 sitemap.index.collect_for_resource 필터 훅)
- 4 리스너 재배선: 공개→색인 / 비공개·삭제→색인 해제 + 재생성 잡 디바운스 디스패치.
3 기여자가 resource_type/resource_id 를 함께 emit 해 전체·증분 identity 를 일치
- 재생성 모드를 SitemapGenerationMode Backed Enum 으로 통일(Command→Job→Manager)
- seo:generate-sitemap 에 --rebuild / --mode 옵션 추가
AbstractSitemapContributor 브리지 base 로 인터페이스를 바꾸지 않고
board/ecommerce/page 기여자를 getUrlsLazy 지연 스트리밍으로 전환해,
게시글이 수백만 건인 사이트에서도 URL 배열을 통째로 적재하지 않는다.
SitemapGenerator 는 capability 를 감지해 지연 경로를 쓰고, base 를
상속하지 않은 제3자 구현체는 기존 getUrls 경로로 그대로 동작한다.
hreflang alternate 집합을 base URL 당 한 번만 계산해 로케일별 <url>
에 재사용하도록 바꿔 계산 비용을 로케일 수의 제곱에서 선형으로 줄였다
(정상 구성 출력은 불변). 다국어 대체 링크 사용 여부 토글과 로케일 수
상한을 추가하고 설정/검증/관리자 UI/다국어(ko·en·ja) 표면을 동반했다.
대용량 사이트에서 사이트맵 생성이 메모리 부족으로 반복 실패하던 문제(공개 )의
1단계로, 전체 URL을 메모리에 적재하던 구조를 스트리밍 분할 생성으로 교체한다.
사이트맵 스트리밍 코어:
- SitemapWriter — 자식 파일 1개 분량만 버퍼링하고 임계 도달 시 flush.
StorageInterface가 append를 제공하지 않아(put이 전체 덮어쓰기) 택한 구조이며,
최대 메모리가 "자식 파일 1개 크기"로 유계가 된다. URL 수(50,000)와
바이트(45MB) 두 임계로 분할해 sitemaps.org 프로토콜을 지킨다.
- 커밋은 _tmp 전량 기록 → live 재배치 → manifest 마지막 기록 순. manifest 존재가
커밋 완료 신호다. 기존 manifest를 먼저 지우지 않아 스왑 중 봇에게 503이 나가지 않는다.
- SitemapFileStore — 읽기측 + 파일 경로 SSoT. SitemapXmlRenderer — escape 단일 출처.
데이터 접근 계층 정리:
- contributor 3종이 쿼리를 직접 조립하던 것을 Repository 위임으로 전환.
MySQL 버퍼드 쿼리에서 cursor는 결과셋 전체를 적재하므로 lazyById를 쓴다.
- SeoCacheStatsService → SeoCacheStatRepository 신설 (집계 4쿼리 → 1쿼리).
- audit 룰 service-direct-data-access의 사각 2건 수정 — 확장 디렉토리 정규식이
상대경로를 매칭하지 못해 *Contributor처럼 접미사 없는 클래스가 전부 미검출이었다.
SEO 캐시 설정 정합화:
- SEO 설정의 캐시 3키가 cache 카테고리 형제 키에 항상 져서 운영자 입력이 조용히
무시되고 있었다(설정을 cache 카테고리로 이관하며 SEO 탭 옛 칸을 미제거).
고급 탭을 기준값으로 두되 SEO 탭에 지정이 있으면 오버라이드하도록 SeoCacheSettings
단일 출처로 정리. "지정 여부"는 null로 판정하므로 기본값을 비웠다.
- 기존 설치는 Upgrade_7_1_0이 이행 — 옛 기본값과 같으면 미설정으로 비우고, 다르면
운영자 의도로 보아 보존한다.
코어 버전 bump와 루트 CHANGELOG는 계획서 결정(D20)에 따라 마감 단계에서 일괄 수행한다.
SeoRenderer.getCssPath가 구 Vite manifest 구조만 파싱해 존재하지 않는 고정 경로 /build/assets/app.css 를 반환, 봇 렌더링 HTML(seo.blade)에서 CSS 404가 발생했다. 해당 파일은 실제 사용자 화면에서 미참조하는 Laravel 기본 빌드 산출물이므로, 담당자 답변 방향대로 참조 자체와 getCssPath 메서드를 제거했다. 봇 스타일은 템플릿 CSS($stylesheets)가 그대로 담당한다.