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>
This commit is contained in:
co-authored by
Claude Opus 5
parent
ec7f20e2ee
commit
8dd7cbaea3
@@ -0,0 +1,265 @@
|
||||
# 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` 지점까지 올려야 한다 |
|
||||
| `develop` → `main` | **fast-forward**. `main`은 `Initial commit` 1개뿐이고 `develop`의 조상이라 머지 커밋이 안 생긴다 |
|
||||
| memento 초기 상태 | 템플릿 그대로 유지 + 식별자만 리브랜딩. `reference-feature`와 `/examples/*`는 **남긴다** |
|
||||
| 템플릿 선수정 | builder/verifier ID를 `package.json`의 `name`에서 파생. 템플릿 자신의 산출물 문자열은 **불변** |
|
||||
| 템플릿에 memento 흔적 | **없다.** 템플릿에 "memento"라는 문자열은 들어가지 않는다 |
|
||||
| 최대 위험 | `pnpm install` 후 게이트가 memento에서도 통과하는지. 특히 `verify:release` 계열이 새 `name`으로 재계산된 ID를 일관되게 쓰는지 |
|
||||
|
||||
실행 순서는 되돌리기 어려운 것이 뒤에 오도록 잡았다: 템플릿 수정 → 게이트 → push → memento 머지 → 리브랜딩 → 게이트.
|
||||
|
||||
---
|
||||
|
||||
## 1. 확정된 결정 (사용자 승인 완료)
|
||||
|
||||
1. **반영 = upstream 추적 부트스트랩.** 1회성 스냅샷 복사안과 "동기화 스크립트만 만들기"안은 기각.
|
||||
근거: 템플릿이 계속 개선되는 레포이므로, 파생 시점에 끊기면 개선분을 수동 diff로 옮겨야 한다.
|
||||
2. **추적 대상은 `upstream/main`.** (`develop`이 아니다.)
|
||||
따라서 템플릿의 `main`을 `develop` 지점까지 올리는 것이 선행 작업이다.
|
||||
3. **memento 초기 상태는 "템플릿 그대로 + 리브랜딩만".**
|
||||
`reference-feature`와 `/examples/*`를 남긴다. 근거: (a) 살아있는 참조 구현이 남아 팀이 패턴을 보고 따라갈 수 있다, (b) 삭제하면 `INVENTORY.md`·테스트 레지스트리·e2e·a11y까지 연쇄로 손봐야 한다, (c) 삭제한 파일은 upstream 머지마다 충돌한다.
|
||||
4. **builder/verifier ID는 템플릿 쪽에서 `package.json`의 `name` 파생으로 바꾼다.**
|
||||
memento에서 문자열만 교체하는 안은 기각. 근거: 그 경우 4개 파일이 영구 충돌 후보가 된다.
|
||||
5. **앱 표시 이름은 `Memento`.** ko/en 카탈로그 모두 동일 표기.
|
||||
6. **템플릿 `main`의 origin push 승인됨.**
|
||||
|
||||
---
|
||||
|
||||
## 2. Part A — 템플릿 선수정: 프로젝트 이름 파생
|
||||
|
||||
### A.1 문제
|
||||
|
||||
레포 이름이 4개 파일에 하드코딩되어 있다.
|
||||
|
||||
| 파일 | 줄 | 값 |
|
||||
|---|---|---|
|
||||
| `scripts/lib/provider-evidence.ts` | 12–13 | `PROMOTION_VERIFIER_ID = "clean-architecture-frontend-template/promotion-verifier"` |
|
||||
| `scripts/lib/local-release-evidence.ts` | 306–307 | `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.ts`에 `PROJECT_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-92`가 `local-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:fs`의 `readFileSync`를 쓴다
|
||||
(선례: `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:93`과
|
||||
`scripts/lib/local-release-evidence.ts:785`다. 나중에 부정 케이스를 추가한다면
|
||||
그쪽은 의도적으로 **틀린 리터럴**을 써야 한다. 상수를 쓰면 자기 자신과 비교하게 된다.
|
||||
|
||||
### A.5 Part A 완료 조건
|
||||
|
||||
```bash
|
||||
corepack pnpm lint
|
||||
corepack pnpm check:types
|
||||
corepack pnpm test:all
|
||||
corepack pnpm build
|
||||
corepack pnpm verify:release
|
||||
```
|
||||
|
||||
추가로, 이 변경이 산출물을 안 바꿨다는 것을 확인한다: 변경 전후로
|
||||
`artifacts/release/provenance.json`의 `predicate.runDetails.builder.id`가 동일해야 한다.
|
||||
|
||||
---
|
||||
|
||||
## 3. Part B — 템플릿 `develop` → `main`
|
||||
|
||||
현재 상태:
|
||||
|
||||
```
|
||||
main 91ae42a Initial commit (커밋 1개)
|
||||
develop ec7f20e refactor: 프론트엔드 리펙토링 (커밋 234개)
|
||||
```
|
||||
|
||||
`main`은 `develop`의 조상이다(`git merge-base --is-ancestor main develop` 통과). 따라서:
|
||||
|
||||
```bash
|
||||
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` 하나.
|
||||
|
||||
```bash
|
||||
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개가 머지로 들어온다.
|
||||
|
||||
이후 동기화:
|
||||
|
||||
```bash
|
||||
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.json`의 `name` 변경은 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/*.json`의 `API_BASE_URL`**
|
||||
`memento-backend`가 아직 빈 레포라 넣을 값이 없다. `http://localhost:8080/` 유지.
|
||||
- **`public/config.json`의 `FEATURE_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 완료 후:
|
||||
|
||||
```bash
|
||||
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.json`의 `name`이 바뀐 상태에서 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 — `develop` → `main` ff 머지, `origin/main` push ← 첫 외부 반영
|
||||
3. Part C — memento에 `upstream` 추가, `upstream/main` 머지, `README.md` 충돌 해소
|
||||
4. Part D — 리브랜딩
|
||||
5. 검증 — memento에서 전 게이트 실행
|
||||
6. memento 커밋 (push 여부는 별도 확인)
|
||||
Reference in New Issue
Block a user