From 4e9226a57d0ab164e0ca8980d2c26bce96c09428 Mon Sep 17 00:00:00 2001 From: DongHyeonka Date: Thu, 10 Sep 2026 19:16:55 +0900 Subject: [PATCH] =?UTF-8?q?fix(studio-save):=20=ED=99=95=EC=9D=B8=20?= =?UTF-8?q?=EC=97=86=EC=9D=B4=20=EC=98=AE=EA=B8=B4=20=EB=AC=B8=EC=9E=A5?= =?UTF-8?q?=EC=9D=84=20=EA=B3=A0=EC=B9=98=EA=B3=A0,=20=EC=95=88=20?= =?UTF-8?q?=EB=8B=BF=EB=8A=94=20=EA=B0=80=EC=A7=80=EC=97=90=20=EC=9C=A0?= =?UTF-8?q?=ED=9A=A8=20=EB=B2=94=EC=9C=84=EB=A5=BC=20=EC=A0=81=EB=8A=94?= =?UTF-8?q?=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 「Decision 은 계약에 삭제 경로가 없다」고 적었는데 틀렸다. ManagementDocumentController :123 에 DELETE /v1/studio/projects/{id}/decisions/{decisionId} 가 있다. 스킬의 문장을 확인 없이 옮겼다. 시험 초안을 CASE 로 고른 이유는 삭제 경로 때문이 아니라 그 기록이 이 배치가 만든 것이라 남의 것이 아니어서다. 멱등 키 길이 검사는 지금 정책에서 안 닿는다. 키가 studio-{op}-{32자}(+{32자})라 길이가 사실상 고정이고 저장 키가 77자다. 죽은 코드가 아니라 키 정책이 바뀌면 살아나는 방어선이라 지우지 않고, 언제 살아나는지를 주석에 적었다. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01Q4vKjQo9KKBBokzxqXLCfk --- scripts/studio-save.py | 9 ++++++++- scripts/tests/test_studio_save.py | 7 +++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/scripts/studio-save.py b/scripts/studio-save.py index ab2f14f..5bc4bb9 100644 --- a/scripts/studio-save.py +++ b/scripts/studio-save.py @@ -178,6 +178,9 @@ def idempotency_key(op: str, record_rel: str, payload: dict | None = None) -> st key = f"studio-{op}-{base}" if payload is not None: key += "-" + _sha256_text(json.dumps(payload, ensure_ascii=False, sort_keys=True))[:32] + # **지금 키 정책에서는 여기 안 온다.** 키가 `studio-{op}-{32자}`(+`-{32자}`)라 길이가 + # 사실상 고정이고 저장 키가 77자다. 죽은 코드가 아니라 **키 정책이 바뀌면 그때 살아나는 + # 방어선**이다 — 경로를 키에 그대로 넣는 식으로 바꾸면 200자를 넘길 수 있다. 지우지 않는다. if len(key) > MAX_KEY_LENGTH: # 서버가 200자 초과를 거절한다 raise Refused(f"멱등 키가 {len(key)}자다. 서버 상한은 {MAX_KEY_LENGTH}자") return key @@ -492,7 +495,11 @@ def plan_requests(record_path: str, doc: dict, document_id: str | None, # # `DELETE /api/v1/studio/cases/{id}` 는 실재한다 # (`ManagementDocumentController.java:72` · `studio-management-v1.yaml:126,246`). - # 종류를 CASE 로 고른 이유가 그것이다 — Decision 은 계약에 삭제 경로가 없다. + # + # **시험 초안을 CASE 로 고른 이유는 삭제 경로 때문이 아니다.** 처음에 「Decision 은 + # 계약에 삭제 경로가 없다」고 적었는데 틀렸다 — `ManagementDocumentController.java:123` + # 에 `DELETE /v1/studio/projects/{id}/decisions/{decisionId}` 가 있다. 스킬의 문장을 + # 확인 없이 옮겼다. **고른 이유는 그 기록이 이 배치가 만든 것이라 남의 것이 아니어서다.** steps.append({ "op": "cleanup", "method": "DELETE", "path": f"/api/v1/studio/cases/{document_id or '<생성된 id>'}", diff --git a/scripts/tests/test_studio_save.py b/scripts/tests/test_studio_save.py index de3239b..0ed5f51 100644 --- a/scripts/tests/test_studio_save.py +++ b/scripts/tests/test_studio_save.py @@ -504,8 +504,11 @@ class HarnessTestPlanTest(unittest.TestCase): def test_the_cleanup_path_is_the_case_delete_endpoint(self): """`DELETE /api/v1/studio/cases/{id}` 는 실재한다 - (`ManagementDocumentController.java:72`). Decision 은 삭제 경로가 없어서 - 시험 초안의 종류를 CASE 로 고른다.""" + (`ManagementDocumentController.java:72`). + + 시험 초안을 CASE 로 고른 이유는 삭제 경로 때문이 아니다 — Decision 에도 + `DELETE /v1/studio/projects/{id}/decisions/{decisionId}` 가 있다(`:123`). + 고른 이유는 그 기록이 이 배치가 만든 것이라 남의 것이 아니어서다.""" steps = ss.plan_requests("docs/p/t/case/x.md", dict(SENT), None, None, harness_test=True) cleanup = next(s for s in steps if s["op"] == "cleanup")