fix: ProblemDetails를 전송 계층이 실제로 만드는 모양 하나로 합친다

Task 3 리뷰 finding(Important): contract.ts의 손수 유지되는 ProblemDetails가
tech-log-studio-contract-contribution.ts의 envelopeError()가 실제로
반환하는 모양과 별도로 정의돼 있었다. envelopeError()는 ApiError의
type/title/status/detail/code/retryable/category/details만 채우므로
ProblemDetails가 갖고 있던 옛 평면 필드(instance/traceId/fieldErrors/
latestDocument/latestPublication/conflictingFields)는 production에서
항상 undefined였다 — mock만 채워서 mock이 아무것도 검증하지 못하는
상태였다.

- contract.ts: ProblemDetails에서 옛 평면 필드를 제거하고 details를
  wire와 같은 union 타입(ValidationErrorDetails | VersionConflictDetails
  | PublicationConflictDetails | null)으로 정확히 준다. 세 타입을 이제
  개별 export한다.
- tech-log-studio-contract-contribution.ts: envelopeError()가
  contract.ts의 ProblemDetails를 그대로 반환 타입으로 쓴다(로컬
  StudioProblemShape 제거) — 이제 한 곳에만 정의가 있다.
- mock-studio-gateway.ts / cursor.ts: fieldErrors/latestDocument/
  conflictingFields/latestPublication을 wire와 같은 자리(details 안)로
  옮긴다.
- mock-studio-gateway.test.ts: 위 이동에 맞춰 details를 캐스트로 좁혀
  읽도록 갱신.

retryable은 optional로 유지했다 — 여러 테스트가 생략하고 만들며, 이번
finding과 무관해 required로 좁히면 관련 없는 파일들이 깨진다.

리뷰가 보류한 2건(asset-upload-transport.ts의 CODES.has 중복 검사,
apiErrorSchema.category가 enum이 아닌 것)은 손대지 않았다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
DongHyeonka
2026-08-18 22:13:33 +09:00
co-authored by Claude Opus 5
parent 25a6b63d27
commit d23a18f659
5 changed files with 67 additions and 48 deletions
@@ -3,6 +3,8 @@ import { test } from "vitest";
import { isStudioGatewayError } from "../../../src/features/tech-log/application/ports/studio-gateway-error.ts";
import type {
ValidationErrorDetails,
VersionConflictDetails,
WorkingCopy,
WorkingCopyInput,
} from "../../../src/features/tech-log/contracts/studio/contract.ts";
@@ -71,8 +73,11 @@ function isProblem(status: number, code: string, paths?: string[]) {
assert.equal(error.status, status);
assert.equal(error.code, code);
if (paths) {
// wire와 같은 자리: `REQUEST_VALIDATION_FAILED`의 detail은
// `ValidationErrorDetails`로 `details`에 있다 (Task 3 fix round 1).
const details = error.problem.details as ValidationErrorDetails;
assert.deepEqual(
error.problem.fieldErrors?.map(({ path }) => path),
details.fieldErrors.map(({ path }) => path),
paths,
);
}
@@ -203,9 +208,8 @@ test("runtime OpenAPI validation enforces discriminators, extras, dates, UUIDs a
}),
(error) => {
assert.ok(isStudioGatewayError(error));
assert.ok(
error.problem.fieldErrors?.some(({ path }) => path === pointer),
);
const details = error.problem.details as ValidationErrorDetails;
assert.ok(details.fieldErrors.some(({ path }) => path === pointer));
return true;
},
);
@@ -575,12 +579,10 @@ test("publication history, immutable snapshots, conflict fixtures and 404 bodies
(error) => {
assert.ok(isStudioGatewayError(error));
assert.equal(error.code, "VERSION_CONFLICT");
assert.deepEqual(error.problem.conflictingFields, [
"/title",
"/summary",
]);
const details = error.problem.details as VersionConflictDetails;
assert.deepEqual(details.conflictingFields, ["/title", "/summary"]);
assert.equal(
error.problem.latestDocument?.document.version,
details.latestDocument.document.version,
conflict.document.version + 1,
);
return true;