fix(studio-save): 양쪽이 빈 칸을 「같다」에 섞지 않는다 (C 의 B-006 ② 수정안)
C 가 V-009 에서 걸렸다 — P-QUESTION-01 의 「되읽기 차이 0」이 배열 다섯이 전부 비어 견줄 것이 없어서 나온 값이었다. same: true 가 「13칸이 다 맞았다」로 읽히는데 실제로는 찬 칸만 맞은 것이다. R14 와 같은 모양이 대조기 안에 한 번 더 있었다. - _is_empty 로 양쪽이 빈 칸을 갈라 emptyBoth 로 낸다. 한쪽만 비면 차이다 - comparedFields 에서도 빠지고 comparedCount 가 실제로 견준 수를 낸다 - 사람이 보는 한 줄에 「N칸 중 M칸을 견줬다. K칸은 양쪽이 비어 견줄 것이 없었다」 C 의 수정안은 comparedFields 를 개수로 바꿨는데 목록을 유지하고 개수를 따로 뒀다 — 이미 목록으로 읽는 회귀가 있고, 어느 칸을 못 봤는지는 이름이 있어야 안다. C-B006 §4 의 대조를 돌렸다. 손으로 센 찬 칸과 도구 출력이 맞는다. CONCEPT 6 / 6 REFERENCE 10 / 10 QUESTION 11 / 11 묶음은 B 가 읽을 수 없어(계약 §3.2) 같은 종류의 기록으로 셌다. python3 -m unittest discover -s scripts/tests — Ran 282 · OK (skipped=13) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q4vKjQo9KKBBokzxqXLCfk
This commit is contained in:
co-authored by
Claude Opus 5
parent
f765a79d6f
commit
252f14c88e
+37
-2
@@ -898,6 +898,11 @@ ITEM_ID_NOTE = ("항목 `id` 만 못 본다 — 보낸 것은 uuid5 이고 서
|
|||||||
"순서가 바뀌는 것은 여전히 걸린다")
|
"순서가 바뀌는 것은 여전히 걸린다")
|
||||||
|
|
||||||
|
|
||||||
|
def _is_empty(v) -> bool:
|
||||||
|
"""값이 「없음」인가. 서버가 빈 배열로, 어댑터가 `None` 으로 두는 자리를 같게 본다."""
|
||||||
|
return v is None or v == "" or v == [] or v == {}
|
||||||
|
|
||||||
|
|
||||||
def _without_item_ids(value):
|
def _without_item_ids(value):
|
||||||
"""항목 배열에서 `id` 만 뺀다. 나머지 칸은 그대로 둔다."""
|
"""항목 배열에서 `id` 만 뺀다. 나머지 칸은 그대로 둔다."""
|
||||||
if not isinstance(value, list):
|
if not isinstance(value, list):
|
||||||
@@ -919,6 +924,21 @@ def normalize(value) -> str:
|
|||||||
return "\n".join(line.rstrip() for line in text.split("\n")).strip()
|
return "\n".join(line.rstrip() for line in text.split("\n")).strip()
|
||||||
|
|
||||||
|
|
||||||
|
def _compare_summary(sent: dict, empty_both: list, differences: list,
|
||||||
|
whitespace_only: list) -> str:
|
||||||
|
"""사람이 보는 한 줄. **그냥 「같음」이면 사정권 밖이 초록으로 돌아온다** —
|
||||||
|
`NO_BEFORE` 를 요약 줄에 적은 것과 같은 이유다."""
|
||||||
|
total = len([f for f in sent if f not in NOT_COMPARED])
|
||||||
|
seen = total - len(empty_both)
|
||||||
|
head = "같음" if not differences and not whitespace_only else (
|
||||||
|
f"다름 {len(differences)}칸" + (f" · 공백만 {len(whitespace_only)}칸"
|
||||||
|
if whitespace_only else ""))
|
||||||
|
tail = (f" — {total}칸 중 {seen}칸을 견줬다. "
|
||||||
|
f"{len(empty_both)}칸은 양쪽이 비어 견줄 것이 없었다"
|
||||||
|
if empty_both else f" — {total}칸을 전부 견줬다")
|
||||||
|
return head + tail
|
||||||
|
|
||||||
|
|
||||||
def compare_saved(sent: dict, fetched: dict) -> dict:
|
def compare_saved(sent: dict, fetched: dict) -> dict:
|
||||||
"""보낸 것과 되읽은 것을 견준다.
|
"""보낸 것과 되읽은 것을 견준다.
|
||||||
|
|
||||||
@@ -932,11 +952,18 @@ def compare_saved(sent: dict, fetched: dict) -> dict:
|
|||||||
적이 있다. 앞이 같고 뒤가 없는 모양은 바뀐 것과 다르게 읽힌다.
|
적이 있다. 앞이 같고 뒤가 없는 모양은 바뀐 것과 다르게 읽힌다.
|
||||||
"""
|
"""
|
||||||
differences, whitespace_only, truncated = [], [], []
|
differences, whitespace_only, truncated = [], [], []
|
||||||
item_ids_skipped = []
|
item_ids_skipped, empty_both = [], []
|
||||||
for field, want in sent.items():
|
for field, want in sent.items():
|
||||||
if field in NOT_COMPARED:
|
if field in NOT_COMPARED:
|
||||||
continue
|
continue
|
||||||
got = fetched.get(field)
|
got = fetched.get(field)
|
||||||
|
if _is_empty(want) and _is_empty(got):
|
||||||
|
# **견준 것이 아니라 견줄 것이 없었던 칸이다.** 이것을 「같다」에 섞으면
|
||||||
|
# 「칸이 다 맞았다」가 「찬 칸만 맞았다」를 가린다 — R14 와 같은 모양이다.
|
||||||
|
# C 가 V-009 에서 걸렸다: `P-QUESTION-01` 의 「되읽기 차이 0」이 배열 다섯이
|
||||||
|
# 전부 비어 **견줄 것이 없어서** 나온 값이었다
|
||||||
|
empty_both.append(field)
|
||||||
|
continue
|
||||||
if field in ITEM_ID_REISSUED and isinstance(want, list):
|
if field in ITEM_ID_REISSUED and isinstance(want, list):
|
||||||
# **빼먹은 것이 아니라 못 보는 것**이므로 무엇을 왜 안 보는지 값에 적는다
|
# **빼먹은 것이 아니라 못 보는 것**이므로 무엇을 왜 안 보는지 값에 적는다
|
||||||
item_ids_skipped.append({"field": field, "items": len(want),
|
item_ids_skipped.append({"field": field, "items": len(want),
|
||||||
@@ -964,7 +991,15 @@ def compare_saved(sent: dict, fetched: dict) -> dict:
|
|||||||
"notCompared": NOT_COMPARED,
|
"notCompared": NOT_COMPARED,
|
||||||
"itemIdsNotCompared": item_ids_skipped,
|
"itemIdsNotCompared": item_ids_skipped,
|
||||||
"unexpected": unexpected,
|
"unexpected": unexpected,
|
||||||
"comparedFields": [f for f in sent if f not in NOT_COMPARED],
|
# **실제로 견준 칸만 남는다.** 양쪽이 빈 칸은 `emptyBoth` 로 갈라 나간다.
|
||||||
|
# C 의 수정안은 이 칸을 개수로 바꿨는데 여기서는 목록을 유지하고 개수를 따로 둔다 —
|
||||||
|
# 이미 목록으로 읽는 회귀가 있고, 어느 칸을 못 봤는지는 이름이 있어야 안다
|
||||||
|
"comparedFields": [f for f in sent
|
||||||
|
if f not in NOT_COMPARED and f not in empty_both],
|
||||||
|
"comparedCount": len([f for f in sent
|
||||||
|
if f not in NOT_COMPARED and f not in empty_both]),
|
||||||
|
"emptyBoth": empty_both,
|
||||||
|
"summary": _compare_summary(sent, empty_both, differences, whitespace_only),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -788,6 +788,57 @@ class HarnessTestPlanTest(unittest.TestCase):
|
|||||||
verify = next(x for x in steps if x["op"] == "verify")
|
verify = next(x for x in steps if x["op"] == "verify")
|
||||||
self.assertIn(pid, verify["expect"]["projectId"])
|
self.assertIn(pid, verify["expect"]["projectId"])
|
||||||
|
|
||||||
|
def test_fields_empty_on_both_sides_are_not_counted_as_compared(self):
|
||||||
|
"""C 가 V-009 에서 걸린 자리. 「되읽기 차이 0」이 배열 다섯이 전부 비어
|
||||||
|
**견줄 것이 없어서** 나온 값이었다 — R14 와 같은 모양이 대조기 안에 한 번 더 있다."""
|
||||||
|
sent = {"title": "t", "summary": "s", "facts": [], "assumptions": [],
|
||||||
|
"unknowns": [], "constraints": [], "options": []}
|
||||||
|
r = ss.compare_saved(sent, dict(sent))
|
||||||
|
self.assertTrue(r["same"])
|
||||||
|
self.assertEqual(2, r["comparedCount"])
|
||||||
|
self.assertEqual(["facts", "assumptions", "unknowns", "constraints", "options"],
|
||||||
|
r["emptyBoth"])
|
||||||
|
# 견준 목록에서도 빠진다 — 어느 칸을 못 봤는지 이름이 남아야 한다
|
||||||
|
self.assertNotIn("facts", r["comparedFields"])
|
||||||
|
|
||||||
|
def test_the_compare_summary_says_how_many_it_looked_at(self):
|
||||||
|
"""사람이 보는 한 줄이 그냥 「같음」이면 사정권 밖이 초록으로 돌아온다."""
|
||||||
|
blind = ss.compare_saved({"title": "t", "facts": []},
|
||||||
|
{"title": "t", "facts": []})["summary"]
|
||||||
|
full = ss.compare_saved({"title": "t", "facts": [{"id": "a", "text": "하나",
|
||||||
|
"order": 0}]},
|
||||||
|
{"title": "t", "facts": [{"id": "x", "text": "하나",
|
||||||
|
"order": 0}]})["summary"]
|
||||||
|
self.assertIn("견줄 것이 없었다", blind)
|
||||||
|
self.assertNotIn("견줄 것이 없었다", full)
|
||||||
|
self.assertNotEqual(blind, full)
|
||||||
|
|
||||||
|
def test_an_empty_field_on_one_side_only_is_still_a_difference(self):
|
||||||
|
"""**양쪽이 비었을 때만 뺀다.** 한쪽만 비면 값이 사라진 것이다."""
|
||||||
|
sent = {"facts": [{"id": "a", "text": "하나", "order": 0}]}
|
||||||
|
r = ss.compare_saved(sent, {"facts": []})
|
||||||
|
self.assertFalse(r["same"])
|
||||||
|
self.assertEqual([], r["emptyBoth"])
|
||||||
|
|
||||||
|
def test_the_tool_count_matches_what_c_counted_by_hand(self):
|
||||||
|
"""C-B006 §4 — 손으로 센 「찬 칸」과 도구 출력이 맞는가. 안 맞으면 그게 발견이다.
|
||||||
|
묶음은 B 가 읽을 수 없으므로(계약 §3.2) 같은 종류의 기록으로 센다."""
|
||||||
|
for path, want in (
|
||||||
|
("docs/keycloak/tech-log-studio/oauth-oidc-auth-boundary/concept/"
|
||||||
|
"concept-bearer-jwt-validation-chain.md", 6),
|
||||||
|
("docs/keycloak/tech-log-studio/oauth-oidc-auth-boundary/reference/"
|
||||||
|
"reference-token-vs-session.md", 10),
|
||||||
|
# `verify-pipeline.py:172` 의 `FORBIDDEN_LITERAL` 이 「옛 저장소 이름 의존」과
|
||||||
|
# 「그 이름의 docs 프로젝트를 가리킴」을 못 가른다(B-008 §7.6). 글자를 쪼개
|
||||||
|
# 피하지 않고 같은 종류의 다른 프로젝트 기록을 쓴다
|
||||||
|
("docs/keycloak/tech-log-studio/oauth-oidc-auth-boundary/question/"
|
||||||
|
"question-bff-state-store.md", 11),
|
||||||
|
):
|
||||||
|
with self.subTest(path=path):
|
||||||
|
import os
|
||||||
|
doc = ss.build_input(os.path.join(ROOT, path))
|
||||||
|
self.assertEqual(want, ss.compare_saved(doc, dict(doc))["comparedCount"])
|
||||||
|
|
||||||
def test_a_harness_plan_still_has_no_publish_path(self):
|
def test_a_harness_plan_still_has_no_publish_path(self):
|
||||||
steps = ss.plan_requests("docs/p/t/case/x.md", dict(SENT), None, None,
|
steps = ss.plan_requests("docs/p/t/case/x.md", dict(SENT), None, None,
|
||||||
harness_test=True)
|
harness_test=True)
|
||||||
|
|||||||
Reference in New Issue
Block a user