Two contract mismatches, both found by driving the API and both invisible
from inside the repository because nothing compares the wire to the spec.
Nullable-but-required. The contract says required means "the key is
present", not "the value is set" — WorkingCopyInputBase spells it out:
"불완전한 초안도 저장할 수 있어야 하므로 필드는 required이되 빈 값과 null을
허용한다". The generator moves `required` straight to @NotNull, so
topicId, projectId, lastVerifiedOn, verifiedOn, decidedOn, decisionStatus
and questionStatus all became non-null, and saving a partial draft failed:
{"projectId": null} → 400 NOT_NULL "Required value is missing"
prepareStudioCodegenSpec already derives a codegen-only copy of the spec,
so the relaxation happens there — 33 properties leave `required` in that
copy and the canonical file is untouched, which matters because the
frontend reads the same file and its reading is the correct one. Value
constraints stay: title still carries @NotNull @Size(max = 120).
all-null / omitted / empty slug 201
title 121 chars 422
slug "Bad Slug!" 422
Error codes. Body validation fell through to the template's handler and
answered 400 VALIDATION_FAILED, a code the Studio contract does not
declare (it knows REQUEST_VALIDATION_FAILED and DOCUMENT_VALIDATION_
FAILED); denials answered AUTHZ_INSUFFICIENT_PERMISSION where the
contract assigns STUDIO_ACCESS_DENIED to 403. The frontend validates the
envelope's code against an enum, so an undeclared code breaks parsing
rather than surfacing as the error it is. Both now map in
StudioExceptionHandler, which is already scoped to the techlog package so
fileserver and healthcheck keep their existing shapes.
body validation 422 REQUEST_VALIDATION_FAILED
denial 403 STUDIO_ACCESS_DENIED
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>