feat: 프로젝트 게시와 홈 focus, 그리고 기록 사이 연결을 실제로 가능하게 한다

계약에 선언만 되어 있고 구현이 없던 네 오퍼레이션과, 의도된 스텁으로 남아 있던
catalog 두 종류가 공개 화면 다섯 곳을 조용히 비워 두고 있었다.

프로젝트 게시 — 프로젝트는 `RecordKind` 에 없어 문서 게시 파이프라인을 타지 못하는데,
공개 조회들(프로젝트 목록·상세·프로필의 "현재 프로젝트"·홈 focus)은 전부
`public_resource_projection` 의 PROJECT 행을 가시성 관문으로 쓴다. 그 행을 세우는
경로가 없었으므로 프로젝트는 만들 수는 있어도 공개될 수는 없었다. 릴리스가 자체 경로를
갖는 것과 같은 이유로, 문서 파이프라인에 끼워 넣지 않고 원본 상태·투영·canonical
route 세 곳을 한 트랜잭션에서 함께 세운다.

홈 focus — `home_focus_config` 는 마이그레이션이 빈 행 하나만 넣어 두었고 그 값을
읽고 쓸 use case 가 없었다. 지목한 대상이 실제로 있는지는 여기서 확인한다. 테이블에
FK 가 없어(설정이 대상보다 오래 살아남는 것을 허용하는 설계다) 없는 id 도 저장되고,
그러면 공개 화면은 조용히 빈 focus 를 그린다 — 저장은 성공했는데 아무것도 안 나오는,
이유를 알 수 없는 실패가 된다. 대상이 공개인지는 확인하지 않는다: 미리 지목해 두고
게시와 동시에 뜨게 하는 것이 정상적인 순서다.

catalog RELATION/EVIDENCE — 「슬라이스 2·5에서 채운다」는 주석과 함께 `List.of()` 로
남아 있었다. 그래서 어떤 기록도 연결 대상 목록을 채울 수 없었다. RELATION 은 작성
중에 고르는 것이라 작업본까지 포함하고(두 문서를 같이 쓰면서 서로 잇는 것이 정상적인
순서다), EVIDENCE 는 읽는 사람이 따라갈 수 있어야 하므로 공개된 것만 포함한다.

계약은 이 네 오퍼레이션을 ProblemDetails 모양으로 두고 있었다. 백엔드가 모든 JSON
응답을 envelope 으로 감싸므로 구현하는 순간 어긋난다 — 나머지와 같은 모양으로 옮겼다.
`/home-focus` 는 케밥 세그먼트라 D19(AIP-122)를 위반해 `/home/focus` 로 나눴다.

함께 고친 것들 (모두 이 작업 전부터 빨간 상태였다):
- `error-codes.yaml` 의 DOCUMENT_NOT_FOUND 가 표면마다 하나씩 두 행이었다. 이
  레지스트리의 식별자는 code 하나뿐이라 로딩 자체가 깨졌고, 그 여파로 거버넌스 테스트
  네 개와 outbox 계약 테스트가 함께 넘어졌다. 같은 코드는 같은 말을 해야 한다.
- 벤더된 계약 세 개의 MANIFEST.sha256 이 실제 파일과 어긋나 있었다.
- ActuatorSecurityHttpTest 는 "DB·Redis 없는 슬라이스"라고 적어 두고 Redis 자동설정을
  막지 않아, localhost:6379 연결 실패가 /actuator/health 를 503 으로 만들었다. 보안
  태세와 무관한 이유로 빨개지던 테스트다.
- 테스트 JVM 힙이 Gradle 기본 512m 이라 Spring context 캐시 + ArchUnit +
  Testcontainers 조합에서 OOM 이 났다. 증상이 테스트 실패가 아니라 "Executor 를 완료할
  수 없음"이어서 원인을 가리켰다.
- APP_SESSION_TIMEOUT 이 env-keys 레지스트리에 없었다.

새 SQL 은 실제 PostgreSQL 위에서 돌린다 — 컴파일도 단위 테스트도 컬럼 이름을 검증하지
못한다는 것이 이 파일이 존재하는 이유다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XEHXspz4rv5pB5wiiSsVDu
This commit is contained in:
DongHyeonka
2026-08-23 18:19:07 +09:00
co-authored by Claude Opus 5
parent 23d82bd250
commit 561d02ae3a
30 changed files with 1210 additions and 105 deletions
+6 -14
View File
@@ -960,7 +960,12 @@ errors:
compatibility_impact: additive
required_test: StudioErrorTest
# source: studio-v1.yaml ApiError.code — DOCUMENT_NOT_FOUND (StudioError.DOCUMENT_NOT_FOUND)
# source: studio-v1.yaml + studio-management-v1.yaml ApiError.code — DOCUMENT_NOT_FOUND
# (StudioError.DOCUMENT_NOT_FOUND, ManagementError.DOCUMENT_NOT_FOUND)
#
# 두 표면이 같은 코드를 쓴다. 이 레지스트리의 식별자는 code 하나뿐이므로 행도 하나다 —
# 한때 표면마다 행을 두고 문구를 다르게 적었고("작업본을 찾을 수 없습니다"), 그 중복이
# 레지스트리 로딩 자체를 깨뜨렸다. 같은 코드는 같은 말을 해야 한다.
- code: DOCUMENT_NOT_FOUND
category: NOT_FOUND
http_status: 404
@@ -1403,19 +1408,6 @@ errors:
runbook_link: null
compatibility_impact: additive
required_test: ManagementErrorRegistryTest
# source: studio-management-v1.yaml ApiError.code — DOCUMENT_NOT_FOUND (ManagementError.DOCUMENT_NOT_FOUND)
- code: DOCUMENT_NOT_FOUND
category: NOT_FOUND
http_status: 404
retryable: false
retry_after_seconds: null
owner_branch: feature-techlog-management-v1
owner_layer: application
client_safe_message: "작업본을 찾을 수 없습니다"
log_level: INFO
runbook_link: null
compatibility_impact: additive
required_test: ManagementErrorRegistryTest
# source: studio-management-v1.yaml ApiError.code — DOCUMENT_PUBLISHED (ManagementError.DOCUMENT_PUBLISHED)
- code: DOCUMENT_PUBLISHED
category: CONFLICT