Files
clean-architecture-frontend…/docs/superpowers/specs/2026-09-20-memento-frontend-template-bootstrap-design.md
T
DongHyeonkaandClaude Opus 5 8dd7cbaea3 docs: design deriving memento-frontend from this template
Records the approved approach for standing up memento-frontend: merge
this template's history into it and keep the template as an `upstream`
remote so later improvements can be merged in.

Two decisions drive the rest of the design. The derived project tracks
`upstream/main`, so this repository's `main` has to be fast-forwarded to
`develop` first. And the derived project keeps the reference feature and
the example routes, so upstream merges stay conflict-free.

The design also covers one change to this repository that is not about
memento: the supply-chain builder and verifier IDs currently hardcode the
repository name in four files, which would make all four permanent
conflict points for every derived project. Deriving them from the
`name` in `package.json` reduces that surface to one line. This
repository's own name does not change, so the emitted ID strings stay
byte-identical.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-20 13:44:40 +09:00

14 KiB
Raw Blame History

memento-frontend를 이 템플릿에서 파생시키기 — 설계서

  • 작성일: 2026-09-20
  • 대상 레포 (둘)
    • 템플릿: /home/donghyeon/workspace/desktop-server-git/clean-architecture-frontend-template (develop, 기준 커밋 ec7f20e)
    • 파생: /home/donghyeon/workspace/desktop-server-git/memento-frontend (main, 기준 커밋 b35e17c)
  • 원격: 둘 다 같은 Gitea (https://git.learn.hyeonworks.com/donghyeon.kang/...)
  • 이 문서는 설계만 한다. 소스 수정·빌드·테스트 실행 없음.

0. 요약 (먼저 읽을 것)

항목 판정
반영 방식 upstream 추적 부트스트랩. 템플릿 히스토리를 memento에 머지하고 upstream 원격으로 남겨 계속 받아온다
추적 브랜치 upstream/main. 그러려면 템플릿의 main을 먼저 develop 지점까지 올려야 한다
developmain fast-forward. mainInitial commit 1개뿐이고 develop의 조상이라 머지 커밋이 안 생긴다
memento 초기 상태 템플릿 그대로 유지 + 식별자만 리브랜딩. reference-feature/examples/*남긴다
템플릿 선수정 builder/verifier ID를 package.jsonname에서 파생. 템플릿 자신의 산출물 문자열은 불변
템플릿에 memento 흔적 없다. 템플릿에 "memento"라는 문자열은 들어가지 않는다
최대 위험 pnpm install 후 게이트가 memento에서도 통과하는지. 특히 verify:release 계열이 새 name으로 재계산된 ID를 일관되게 쓰는지

실행 순서는 되돌리기 어려운 것이 뒤에 오도록 잡았다: 템플릿 수정 → 게이트 → push → memento 머지 → 리브랜딩 → 게이트.


1. 확정된 결정 (사용자 승인 완료)

  1. 반영 = upstream 추적 부트스트랩. 1회성 스냅샷 복사안과 "동기화 스크립트만 만들기"안은 기각. 근거: 템플릿이 계속 개선되는 레포이므로, 파생 시점에 끊기면 개선분을 수동 diff로 옮겨야 한다.
  2. 추적 대상은 upstream/main. (develop이 아니다.) 따라서 템플릿의 maindevelop 지점까지 올리는 것이 선행 작업이다.
  3. memento 초기 상태는 "템플릿 그대로 + 리브랜딩만". reference-feature/examples/*를 남긴다. 근거: (a) 살아있는 참조 구현이 남아 팀이 패턴을 보고 따라갈 수 있다, (b) 삭제하면 INVENTORY.md·테스트 레지스트리·e2e·a11y까지 연쇄로 손봐야 한다, (c) 삭제한 파일은 upstream 머지마다 충돌한다.
  4. builder/verifier ID는 템플릿 쪽에서 package.jsonname 파생으로 바꾼다. memento에서 문자열만 교체하는 안은 기각. 근거: 그 경우 4개 파일이 영구 충돌 후보가 된다.
  5. 앱 표시 이름은 Memento. ko/en 카탈로그 모두 동일 표기.
  6. 템플릿 main의 origin push 승인됨.

2. Part A — 템플릿 선수정: 프로젝트 이름 파생

A.1 문제

레포 이름이 4개 파일에 하드코딩되어 있다.

파일
scripts/lib/provider-evidence.ts 1213 PROMOTION_VERIFIER_ID = "clean-architecture-frontend-template/promotion-verifier"
scripts/lib/local-release-evidence.ts 306307 LOCAL_EVIDENCE_VERIFIER_ID = "clean-architecture-frontend-template/local-evidence-verifier"
scripts/generate-supply-chain.ts 273 builder: { id: "local:clean-architecture-frontend-template" }
tests/unit/security-followup-fixture.ts 38, 183, 499 위 두 ID의 리터럴 복사본

파생 프로젝트는 이 4개 파일을 고쳐야 하고, 그 순간 4개 파일 모두가 upstream 머지의 영구 충돌 지점이 된다.

A.2 해법: scripts/lib/supply-chain.tsPROJECT_NAME을 둔다

새 파일을 만들지 않는 것이 핵심이다. scripts/lib/supply-chain.ts

  • 세 소비자가 이미 전부 임포트하고 있고 (provider-evidence.ts:5-8, local-release-evidence.ts:70, generate-supply-chain.ts:26)
  • LOCAL_EVIDENCE_VERIFIER_SOURCE_PATHS(scripts/lib/release-candidate.ts:38-58)에 이미 등재되어 있다.

새 파일을 만들면 이렇게 된다:

  • tests/unit/security-followup.test.ts:79-92local-release-evidence.ts의 모든 상대 .ts import를 뽑아 LOCAL_EVIDENCE_VERIFIER_SOURCE_PATHS의 부분집합인지 검사한다. 새 파일을 임포트하면 이 테스트가 깨진다.
  • 목록을 늘리면 release-candidate.ts:71을 통해 릴리스 증적의 대상 파일 집합이 함께 바뀐다.

supply-chain.ts는 이미 임포트되어 있고 이미 목록에 있으므로 둘 다 발생하지 않는다.

A.3 변경 내용

scripts/lib/supply-chain.ts에 추가:

  • 리포지토리 루트의 package.json에서 name을 읽어 PROJECT_NAME으로 노출한다.
  • 읽기는 모듈 로드 시점 1회. node:fsreadFileSync를 쓴다 (선례: scripts/check-adapter-inventory.ts). 기존 export const 소비자들이 동기 상수를 기대하므로 비동기 readFile로는 시그니처가 바뀐다.
  • 루트 해석은 new URL("../../package.json", import.meta.url) 로 고정한다. 레포의 기존 관용구가 이것이다(scripts/run-vitest.ts:26, scripts/check-architecture.ts:74). import.meta.dirname은 이 레포에서 쓰인 적이 없으므로 도입하지 않는다. process.cwd()에 의존하면 다른 디렉터리에서 스크립트를 호출했을 때 값이 달라진다.
  • name이 없거나 빈 문자열이면 던진다. 조용한 폴백은 증적(provenance)에 잘못된 identity를 박아 넣는다.

파생 상수:

상수 정의 템플릿에서의 값
PROMOTION_VERIFIER_ID `${PROJECT_NAME}/promotion-verifier` clean-architecture-frontend-template/promotion-verifier
LOCAL_EVIDENCE_VERIFIER_ID `${PROJECT_NAME}/local-evidence-verifier` clean-architecture-frontend-template/local-evidence-verifier
supply-chain builder id `local:${PROJECT_NAME}` local:clean-architecture-frontend-template

템플릿의 package.json name은 안 바꾼다. 따라서 세 값 모두 현재와 문자 단위로 동일하다. 이 변경은 템플릿의 산출물을 바꾸지 않는다. 바뀌는 것은 값의 출처뿐이다.

A.4 테스트 픽스처

tests/unit/security-followup-fixture.ts의 리터럴 3곳(38, 183, 499)을 위 상수 import로 교체한다.

조사 결과 부정 케이스는 없다. 세 곳 모두 status: "PASS" 픽스처이고, 의도적으로 틀린 verifier ID를 넣어 거부를 확인하는 테스트는 tests/ 어디에도 없다. 따라서 세 곳 전부 상수로 바꿔도 테스트가 무력화되지 않는다.

참고로 불일치를 거부하는 코드는 scripts/lib/exact-promotion-bundle.ts:93scripts/lib/local-release-evidence.ts:785다. 나중에 부정 케이스를 추가한다면 그쪽은 의도적으로 틀린 리터럴을 써야 한다. 상수를 쓰면 자기 자신과 비교하게 된다.

A.5 Part A 완료 조건

corepack pnpm lint
corepack pnpm check:types
corepack pnpm test:all
corepack pnpm build
corepack pnpm verify:release

추가로, 이 변경이 산출물을 안 바꿨다는 것을 확인한다: 변경 전후로 artifacts/release/provenance.jsonpredicate.runDetails.builder.id가 동일해야 한다.


3. Part B — 템플릿 developmain

현재 상태:

main    91ae42a Initial commit          (커밋 1개)
develop ec7f20e refactor: 프론트엔드 리펙토링 (커밋 234개)

maindevelop의 조상이다(git merge-base --is-ancestor main develop 통과). 따라서:

git checkout main
git merge --ff-only develop
git push origin main

--ff-only를 쓰는 이유: main이 고유 커밋을 하나도 갖고 있지 않아 --no-ff 머지 커밋은 아무 정보도 담지 못한다. --ff-only는 "조상이 아니면 실패"라는 안전장치 역할도 한다.

Part A의 커밋이 develop에 먼저 올라가야 하므로 Part A → Part B 순서를 지킨다. (Part A가 develop에 커밋되어도 main은 여전히 조상이므로 ff는 유지된다.)


4. Part C — memento-frontend 부트스트랩

현재 상태: main에 커밋 2개 (bce7c4c Initial commit, b35e17c README.md 업데이트), 파일은 README.md 하나.

cd /home/donghyeon/workspace/desktop-server-git/memento-frontend
git remote add upstream https://git.learn.hyeonworks.com/donghyeon.kang/clean-architecture-frontend-template
git fetch upstream
git merge upstream/main --allow-unrelated-histories
  • --allow-unrelated-histories가 필요한 이유: 두 레포는 공통 조상이 없다.
  • 예상 충돌: README.md 1건(양쪽에 존재하는 유일한 경로). 템플릿 쪽을 채택한 뒤 Part D에서 교체한다.
  • 결과: memento의 커밋 2개가 first-parent 선상에 보존되고, 템플릿 234개가 머지로 들어온다.

이후 동기화:

git fetch upstream && git merge upstream/main

5. Part D — 리브랜딩

D.1 바꾸는 곳

파일 위치 현재 변경 후
package.json name clean-architecture-frontend-template memento-frontend
index.html 7 Clean Architecture Frontend Memento
src/presentation/i18n/catalog.ts 6, 165 (common.appName) Frontend Skeleton Memento
src/presentation/i18n/catalog.ts 44, 203 (route.APP_HOME.title) Clean Architecture Frontend Memento
src/presentation/pages/home-page.tsx 43 Clean Architecture Frontend Memento
src/contracts/storage-keys.ts 1 (APP_NAMESPACE) ca-frontend memento
README.md 전체 템플릿 설명 memento 설명 + upstream 동기화 절차

package.jsonname 변경은 Part A 덕분에 자동으로 verifier/builder ID까지 memento 값으로 전파된다.

APP_NAMESPACE 변경 시 주의: 이 값은 storage-keys.ts:140에서 ${APP_NAMESPACE}:${scope}:v${schemaVersion}:${name} 형태로 브라우저 저장소 키를 만든다. 템플릿이 이미 쓰던 키가 남아 있는 브라우저에서는 기존 데이터가 고아가 된다. 신규 프로젝트라 실제 영향은 없지만, 키 형식을 검사하는 테스트가 있으면 같이 갱신해야 한다.

D.2 바꾸지 않는 곳 (의도적)

  • docs/superpowers/specs/*, docs/reviews/*, docs/architecture/* 안의 템플릿 이름 이들은 템플릿에서 실제로 일어난 작업의 기록이다. 이름을 바꾸면 기록이 거짓이 되고, upstream 머지 충돌만 늘어난다. (해당 파일 7개)
  • config/runtime/*.jsonAPI_BASE_URL memento-backend가 아직 빈 레포라 넣을 값이 없다. http://localhost:8080/ 유지.
  • public/config.jsonFEATURE_OVERRIDES["reference-feature"] 결정 3에 따라 reference-feature를 남기므로 그대로 둔다.

D.3 memento README.md

최소한 다음을 담는다. memento의 도메인이 아직 정해지지 않았으므로 제품 설명은 쓰지 않는다.

  1. 이 레포가 clean-architecture-frontend-template에서 파생되었다는 사실
  2. upstream 동기화 절차 (git fetch upstream && git merge upstream/main)
  3. 로컬 실행 절차 (템플릿 README에서 승계)
  4. 리브랜딩된 지점 목록 — 다음 사람이 upstream 충돌을 만났을 때 볼 곳

6. 검증

memento-frontend에서, Part D 완료 후:

corepack pnpm install --frozen-lockfile
corepack pnpm lint
corepack pnpm check:types
corepack pnpm check:architecture
corepack pnpm check:adapter-inventory
corepack pnpm test:all
corepack pnpm build
corepack pnpm verify:release

verify:release를 포함하는 이유: Part A가 손댄 경로가 정확히 이 게이트의 대상이고, package.jsonname이 바뀐 상태에서 ID 파생이 일관되게 동작하는지는 여기서만 드러난다.

.nvmrc의 Node 24.14.0과 Corepack이 필요하다.


7. 비범위 (이번에 하지 않는 것)

  • memento의 도메인 기능 설계·구현. 이번 작업의 산출물은 "빌드되고 게이트를 통과하는 빈 스캐폴드"까지다.
  • memento-backend 연동. 해당 레포도 아직 비어 있다.
  • reference-feature 제거. 결정 3에 따라 남긴다. 나중에 제거할 때는 별도 작업으로 다룬다.
  • .gitea/workflows/quality-gates.yml의 CI 활성화 검증. 파일은 그대로 넘어가지만 Gitea Actions 러너 설정은 이 작업 범위 밖이다.
  • 템플릿의 develop 브랜치 정리나 refactor/platform-consumer-migration 브랜치 처리.

8. 리스크

리스크 영향 완화
Part A에서 readFileSync 경로가 호출 컨텍스트에 따라 달라짐 증적에 잘못된 identity new URL(..., import.meta.url) 기준 고정, process.cwd() 사용 금지
Part A가 local-release-evidence.ts에 새 import를 추가함 security-followup.test.ts:79-92 실패 이미 임포트 중인 supply-chain.ts만 쓴다. 새 파일을 만들지 않는다
memento에서 pnpm install 후 게이트 실패 부트스트랩 미완 Part B push 전에 템플릿에서 전 게이트를 먼저 통과시킨다
--allow-unrelated-histories 머지에서 예상 밖 충돌 수작업 증가 충돌 파일이 README.md 외에 나오면 중단하고 보고
main ff 머지 실패 Part B 중단 --ff-only가 알아서 실패시킨다. 그 경우 main에 예상 못한 커밋이 생긴 것이므로 재조사

9. 실행 순서 (되돌리기 어려운 것이 뒤)

  1. Part A — 템플릿 develop에서 ID 파생으로 변경, 게이트 통과, 커밋
  2. Part B — developmain ff 머지, origin/main push ← 첫 외부 반영
  3. Part C — memento에 upstream 추가, upstream/main 머지, README.md 충돌 해소
  4. Part D — 리브랜딩
  5. 검증 — memento에서 전 게이트 실행
  6. memento 커밋 (push 여부는 별도 확인)