diff --git a/.agents/skills/publishing-tech-log-to-studio/SKILL.md b/.agents/skills/publishing-tech-log-to-studio/SKILL.md index bedbdf6..7774ebb 100644 --- a/.agents/skills/publishing-tech-log-to-studio/SKILL.md +++ b/.agents/skills/publishing-tech-log-to-studio/SKILL.md @@ -128,9 +128,25 @@ python3 scripts/verify-tech-log-tree.py <프로젝트> ## 시험용 초안을 남기지 않는다 -확인하려고 만든 작업본은 지운다. 다만 **Decision 은 계약에 삭제 경로가 없다** — 확인용으로 -만들지 않는 편이 낫다. 관계로 참조된 문서도 지워지지 않는다(`DOCUMENT_IN_USE`). 참조하는 쪽의 -관계를 먼저 끊는다. +확인하려고 만든 작업본은 지운다. **다섯 종류 전부 삭제 경로가 있다** — +`tech-log-backend` @ `a000f87` 의 `ManagementDocumentController` 에서 센 것이다. + +| 종류 | 경로 | +|---|---| +| Case | `DELETE /v1/studio/cases/{id}` (`:72`) | +| Reference | `DELETE /v1/studio/references/{id}` (`:81`) | +| Concept | `DELETE /v1/studio/concepts/{id}` (`:95`) | +| Question | `DELETE /v1/studio/questions/{id}` (`:114`) | +| Decision | `DELETE /v1/studio/projects/{id}/decisions/{decisionId}` (`:123`) | + +**전에 이 자리에 「Decision 은 계약에 삭제 경로가 없다」고 적혀 있었고 그것은 틀렸다.** +확인 없이 적힌 문장이 옮겨 다녔다 — 이 배치에서 그 문장을 코드 주석과 보고서로 다시 옮긴 +일이 있었다. **서버를 서술할 때는 확인한 리비전을 함께 적는다.** 리비전이 없으면 서버가 +바뀌어도 아무도 모른다. + +**지워지지 않는 것은 따로 있다.** 게시한 적이 있으면 `DOCUMENT_PUBLISHED` 로 409 가 나고 +(「공개된 기록은 삭제할 수 없습니다」), 관계로 참조된 문서는 `DOCUMENT_IN_USE` 로 막힌다 +(「이 기록을 참조하는 곳이 있어 삭제할 수 없습니다」). 참조하는 쪽의 관계를 먼저 끊는다. ## 실패했을 때 diff --git a/scripts/tests/test_studio_save.py b/scripts/tests/test_studio_save.py index 0ed5f51..30f0b52 100644 --- a/scripts/tests/test_studio_save.py +++ b/scripts/tests/test_studio_save.py @@ -533,3 +533,37 @@ class HarnessTestPlanTest(unittest.TestCase): for s in steps: self.assertNotIn("/publish", s["path"]) self.assertNotIn("/unpublish", s["path"]) + + +class KeyStabilityTest(unittest.TestCase): + """저장 멱등 키가 **필드 순서에 안 흔들린다.** + + `idempotency_key` 가 `json.dumps(..., sort_keys=True)` 로 본문을 직렬화한다. + **그 효과를 노리고 넣은 것이 아니라** JSON 을 안정적으로 만들려고 넣은 것이 멱등성을 + 지켰다 — 다시 직렬화한 재시도(같은 내용, 다른 필드 순서)가 새 저장으로 잡히지 않는다. + + **의도하지 않은 성질에 기대는 코드는 회귀로 못박지 않으면 다음 사람이 지운다.** + `sort_keys` 를 빼면 이 시험이 깨진다. + """ + + REL = "docs/p/tech-log-studio/t/case/x.md" + + def test_field_order_does_not_change_the_save_key(self): + one = {"expectedVersion": 3, + "document": {"kind": "CASE", "title": "가", "slug": "x"}} + two = {"document": {"slug": "x", "title": "가", "kind": "CASE"}, + "expectedVersion": 3} + self.assertEqual(ss.idempotency_key("save", self.REL, one), + ss.idempotency_key("save", self.REL, two)) + + def test_content_still_changes_the_save_key(self): + """대조군. 순서에 안 흔들린다고 내용에도 안 흔들리면 그건 다른 결함이다.""" + one = {"expectedVersion": 3, "document": {"title": "가"}} + two = {"expectedVersion": 3, "document": {"title": "나"}} + self.assertNotEqual(ss.idempotency_key("save", self.REL, one), + ss.idempotency_key("save", self.REL, two)) + + def test_the_create_key_does_not_depend_on_a_payload(self): + """만들기 키는 경로만으로 만든다. 재시도가 문서를 둘 만들지 않는다.""" + self.assertEqual(ss.idempotency_key("create", self.REL), + ss.idempotency_key("create", self.REL))