feat: let an author delete a working copy

An author who opens a draft and thinks better of it had no way out — the
working-copy list could create, edit, validate and publish, and that was all.
The three delete operations existed in the contract with no implementation.

Deleting is not a cascade, which is the part worth being careful about. A
document's own rows follow it: the detail row, its tags, the relations it
points outward. But five tables reference `document` without ON DELETE CASCADE
— another document's relation target, a question's link, a project's
membership, a topic's featured list, a decision's source case — and two do the
same for `open_question`. Deleting through any of them is a foreign-key
violation, which reaches the author as a 500 that explains nothing. So the
delete checks first and refuses with DOCUMENT_IN_USE, the same refusal
TOPIC_IN_USE already makes. Quietly editing someone else's record to make room
is the worse option.

A published record is refused outright. Public pages, search and other records
link to it, and one that vanishes leaves all of them pointing at nothing —
unpublishing is the way out, and it already exists.

Case and Reference share one table split by `document_type`, so the type is
part of the lookup: without it, the Case route would happily delete a
Reference. Decisions have no delete at all, and that is the contract's
judgment rather than an omission — accept, reject and supersede record what
happened instead of erasing it.
This commit is contained in:
DongHyeonka
2026-08-21 13:30:37 +09:00
parent 386f360122
commit 1befdc37a4
11 changed files with 495 additions and 48 deletions
+65
View File
@@ -1402,4 +1402,69 @@ errors:
log_level: INFO
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
http_status: 409
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 — QUESTION_NOT_FOUND (ManagementError.QUESTION_NOT_FOUND)
- code: QUESTION_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_IN_USE (ManagementError.DOCUMENT_IN_USE)
- code: DOCUMENT_IN_USE
category: CONFLICT
http_status: 409
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 — QUESTION_IN_USE (ManagementError.QUESTION_IN_USE)
- code: QUESTION_IN_USE
category: CONFLICT
http_status: 409
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