Skip to content

fix: 타입 리터럴 파서가 전각 숫자를 거부한다 - #336

Open
SeoyunL wants to merge 9 commits into
semantic-reasoning:mainfrom
SeoyunL:up-331
Open

fix: 타입 리터럴 파서가 전각 숫자를 거부한다#336
SeoyunL wants to merge 9 commits into
semantic-reasoning:mainfrom
SeoyunL:up-331

Conversation

@SeoyunL

@SeoyunL SeoyunL commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Closes #331.

무엇을 고쳤나

factlog/literal_types.py 의 정규식 9개에서 수치 그룹을 \d[0-9] 로 좁혀, 전각 숫자가 typed 리터럴로 파싱되지 않게 한다.

이슈가 제시한 두 방향 중 (A) 거부를 택했다. (B) 접기(merge 단계에서 저장 문자열을 ASCII 로 폴드)는 tools/merge_candidates.py 를 고쳐야 하고, 무엇보다 사람이 accept 한 문자열과 KB 문자열을 다르게 만든다 — 사람 게이트의 의미를 침식한다. 모듈 계약이 "never guesses" 인데 전각 수용은 "이 문자열은 ASCII 와 같은 값" 이라는 추측이다.

(A) 를 지지하는 근거가 하나 더 있다. factlog/cli.py:1762-1764factlog status 경로는 이미 원문 객체로 conflict 를 센다. 반면 tools/check_conflicts.py 만 스칼라로 병합해 0 을 보고한다 — 두 경로가 현재 서로 모순이고, 전각을 거부하면 그 불일치가 해소된다.

이 수정이 남기는 것

이슈의 세 증상 중 (1) typed 스칼라 조용한 병합만 닫는다.

  • (2) 질의 미스 — 잔존, 한 하위 케이스에서는 악화. 기존 KB 에 amount(100,"억") 가 저장돼 있으면 amount(100,억) 질의가 이제 미스한다. 정책 (A) 하에서 전각 항은 유효한 amount 가 아니므로 정준화 대상이 없다 — 의도된 결과다.
  • (3) 엔티티 분리 — 잔존. typed 선언이 없는 관계는 애초에 파서를 타지 않는다.

표면화 경로는 둘, 구멍은 하나. typed 선언 관계는 _project_typed_relations 가 stderr 경고를 내고, attribute 로 선언되지 않은 관계는 entity_audit 가 literal suspect 로 보고한다. 남는 구멍은 attribute 선언은 있고 typed 는 아닌 관계 하나뿐이다.

기존 KB 마이그레이션 (중요)

전각 값이 있는 KB 는 tools/check_conflicts.pyexit 1 로 실패할 수 있다 — 경고가 아니라 게이트 실패다. 전각 값이 ("raw", 원문) 키로 떨어져 단일값 관계에서 두 값이 되기 때문이다.

조용한 쪽도 있다: 위 (2) 의 질의 미스는 엔진이 확인한 "없음" 과 구분되지 않는다.

둘 다 소스를 ASCII 로 고쳐 재수집하면 해소된다. 레포에 CHANGELOG 가 없어 docs/reference/typed-relations.md(+.en.md)에 적었다.

구현 판단

  • re.ASCII 플래그를 쓰지 않았다. \s 까지 좁혀 date(2020,<U+3000>1) 이 조용히 깨진다(실측). 명시적 [0-9] 로만 좁혔고, 이 함정을 테스트로 고정했다.
  • _AMOUNT_RE\D+ 단위 그룹은 유지. \DNd 여집합이라 전각이 단위로 새지 않는다.
  • 2차 방어 가드를 추가하지 않았다. 정규식이 유일한 초크포인트이고, 중복 가드는 equivalent mutant 만 만든다.
  • tools/entity_audit.py_LITERAL_RE일부러 좁히지 않았다 — 좁히면 전각 값이 audit 에서도 사라져 두 번째 표면화 경로가 닫힌다. 주석과 테스트로 이 발산을 고정했다.

검증

  • pytest tests/unit706 passed / 13 skipped (baseline 655/13, 신규 51건)
  • uvx ruff@0.15.17 check . — clean (CI 핀 버전)
  • 뮤테이션: 정규식 9개를 하나씩 \d 로 되돌려 전체 스위트 실행 → 9/9 전부 사살, 생존 0
  • 차등 퍼징 40,000+ 건 (공개 함수 8개, ASCII 입력) — old↔new 차이 0, 전각 누수 0
  • tests/*.sh 하니스 40개 — upstream/main 과 동일 (실패는 환경 기인 2건뿐: golden.sh step 2 = pyrewire, test_hwp_ingest.sh = 선행 결함)
  • pyrewire 1.0.3 설치 상태에서 run_logic_check rc=0, logic_report.txt golden MATCH

후속으로 분리한 것

  • tools/corroboration.py 의 경쟁 버킷이 세 축 전부 원문 키 (별칭 정준화·typed 스칼라 폴딩 없음)
  • attribute 선언은 있고 typed 는 아닌 관계의 전각 값 무표면

SeoyunL added 9 commits July 29, 2026 11:37
literal_types 의 아홉 정규식이 모두 `\d` 로 숫자를 인식했다. 파이썬의 `\d` 는
유니코드 Nd 범주 전체에 매치하므로 `100억` 이 `100억` 과 **같은 스칼라**로
파싱되면서 relation/3 에 저장되는 객체 문자열은 서로 달랐다. 그래서 "같은
값인가" 에 대한 답이 코드 경로마다 갈렸다 — typed 관계 아래서는 병합되고,
객체 매치 질의에서는 조용히 미스하고, typed 선언이 없으면 별개 엔티티로 남았다.

수치 그룹 21곳을 정규식 단위로 다시 써 `[0-9]` 로 좁힌다. 전각 값은 "파싱 실패
→ 타입 없이 적재" 라는 기존 계약을 그대로 타고, _project_typed_relations 의
경고로 표면화된다. 접기(fold)를 택하지 않은 이유는 그것이 저장된 사실 문자열을
다시 쓰는 결정이기 때문이다.

거부가 접기보다 일관된 근거가 하나 더 있다. cli.py 의 status 경로는 이미 원문
객체로 conflict 를 세는데 check_conflicts.py 만 스칼라로 병합해 0 을 보고한다.
두 경로가 현재 서로 모순이며, 전각을 거부하면 그 불일치가 해소된다.

re.ASCII 는 쓰지 않는다. 같은 플래그가 `\s` 도 좁혀 `date(2020,<U+3000>1)` 이
조용히 깨진다. `\D+` 단위 그룹은 그대로 둔다 — `\D` 는 Nd 의 여집합이라 이미
전각을 배제한다.

모듈 밖 세 경로의 동작이 함께 바뀐다(의도된 결과, 데이터 재작성 없음).
merge_candidates 의 dedup 키가 전각 두 표기를 더는 합치지 않고, 기존 KB 에
저장된 전각 값은 _canonical_value 로 정준화되지 않아 질의가 미스하며, humanize
는 전각 term 을 원문 그대로 돌려준다. 모듈 docstring 에 적어 두었다.

잔여 증상도 함께 적었다. 표면화는 typed 로 **선언된** 관계에서만 일어난다.
attribute 선언만 있고 typed 가 아닌 관계의 전각 값은 여전히 어디에도 뜨지 않는다.

업스트림 #331
타입 지정 리터럴은 ASCII 숫자만 파싱한다는 계약과, 전각을 조용히 접지 않는
이유(저장된 사실 문자열을 다시 쓰게 된다)를 ko/en 양쪽에 적는다.

마이그레이션 주의도 함께 적는다. 이 규칙 이전에 수집된 전각 값이 남아 있으면
check_conflicts 가 새로 exit 1 로 실패할 수 있다 — 경고가 아니라 게이트 실패다.
전에는 `100억` 과 `100억` 이 같은 스칼라로 접혀 한 값이었지만 이제 전각 쪽이
원문 문자열로 남아, 같은 주어의 단일값 관계에서 두 값이 되기 때문이다. 해소는
소스를 ASCII 로 고쳐 재수집하는 것이다. 레포에 CHANGELOG 가 없어 이 문서가
유일한 안내 지점이다.

업스트림 #331
_LITERAL_RE 는 냄새 탐지기라 literal_types 의 엄격한 ASCII 전용 계약과 일부러
다르다. 여기서는 `\d` 가 전각까지 잡아야, 파서가 거부한 전각 값이 audit 에는
남아 사람 눈에 띈다. 좁히면 그 값이 어느 경로에서도 안 보이게 된다.

정규식은 그대로 두고 주석만 남긴다.

업스트림 #331
`amount('10억')` 과 `number('123')` 은 홑따옴표를 쓴다. compound 문법에는
홑따옴표가 없으므로 이 둘은 전각과 무관하게 baseline 에서도 이미 None 이었다 —
즉 전각 거부를 증명하지 못하는 vacuous 케이스였다.

baseline 에서 각각 1000000000 / 123000 을 돌려주던 `amount(10,"억")` 과
`number("123")` 로 바꾼다. 이제 두 케이스 모두 이 수정이 없으면 실패한다.

업스트림 #331
모듈 밖 소비자를 전수로 다시 세니 셋이 아니라 넷이다. 빠져 있던
check_conflicts._group_key 가 나머지 셋보다 무거운 경로다 — 나머지는 "데이터
재작성 없음" 이 요점이지만, 이쪽은 통과하던 게이트가 exit 1 로 뒤집힌다. 전각
객체가 ("raw", obj) 로 떨어져 ASCII 쌍둥이의 ("scalar", …) 와 갈리고, 단일값
관계에서 두 값이 되기 때문이다. 사용자 문서에는 적혀 있었지만 코드를 읽는
사람은 놓칠 자리였다.

merge dedup 서술도 결과를 절반만 적고 있었다. 안 합쳐지는 것에 더해, 새로
머지되는 전각 행의 저장 문자열 자체가 따옴표 정준형으로 재작성되지 않고 원문
그대로 남는다(merge_candidates 는 canonical_amount 가 None 이 아닐 때만 객체를
덮어쓴다). 기존 KB 행이 불변이라는 주장은 그대로다.

표면화 경로도 정확히 다시 적었다. 이슈 AC3 이 "경고 또는 audit" 이라 쓴 대로
경로는 둘이다 — typed 선언된 관계는 투영 경고, attribute 로 선언되지 **않은**
관계는 entity_audit 의 literal suspects(_LITERAL_RE 가 일부러 느슨해 전각을
계속 잡는다). 진짜 구멍은 그 사이, attribute 선언은 있고 typed 는 아닌 관계
하나뿐이다. 이전 문구는 이 구멍을 실제보다 넓게 적고 있었다.

업스트림 #331
TestLiteralReConsistency 는 "must not drift" 를 표방하면서 ASCII 8건만 핀했다.
그래서 누가 일관성을 이유로 _LITERAL_RE 를 [0-9] 로 좁혀도 아무 테스트도 깨지지
않는다 — 그런데 그 좁힘은 표면화 경로 하나를 통째로 닫는다. attribute 로 선언되지
않은 관계의 전각 값은 _LITERAL_RE 가 잡아 주어야 literal suspects 로 뜬다.

_LITERAL_RE.match 가 True 인 것과 normalize 가 None 인 것을 한 테스트에서 함께
단언해, 느슨함과 엄격함이 **일부러** 다르다는 계약을 테스트가 들고 있게 한다.

뮤턴트 확인: _LITERAL_RE 의 \d 를 [0-9] 로 좁히면 이 4건이 실패하고 나머지는
전부 통과한다(= 이전에는 이 계약을 아무도 붙들지 않았다는 뜻이기도 하다).

업스트림 #331
문서가 경고한 check_conflicts exit 1 은 이번 변경이 사용자 파이프라인을 실제로
깨뜨릴 수 있는 유일한 동작인데, 문서 문장 하나에만 기대고 있었다. 나중에
_group_key 에 전각 폴백이 들어가 조용히 되돌아가도 아무것도 실패하지 않는다.

_group_key('100억') == ("raw", …) 와 ASCII 쌍둥이의 ("scalar", …) 가 갈리는
것, 그래서 단일값 관계에서 두 값이 되어 CONFLICT 가 뜨는 것을 amount·date 로
고정한다. 전각 한 표기만 있는 KB 는 여전히 conflict 가 아니라는 것도 함께 —
raw 로의 강등이 없던 모순을 만들어내면 안 된다.

check_conflicts.py 는 읽기만 했고 수정하지 않았다.

뮤턴트 확인: _group_key 가 normalize 전에 NFKC 로 접게 만들면 이 중 3건이
실패한다(전각 단독 케이스는 폴드 하에서도 초록이라 통과 — 의도대로다).

업스트림 #331
마이그레이션 절에는 check_conflicts exit 1 만 있었다. 그런데 이슈 본문이 문제로
지목한 것은 오히려 조용한 쪽이다 — "검증된 부정(engine says no)과 구분되지
않는다". 시끄러운 실패보다 사용자 대면 문서에 들어갈 자격이 더 있다.

기존 KB 에 전각 amount compound term 이 저장돼 있으면 따옴표 없이 쓴 질의가 이제
조용히 미스한다. 전각 항이 더는 유효한 amount 가 아니라서 저장본과 같은 정준형으로
접히지 않기 때문이다. 실측:

  before: canon('amount(100,억)') = 'amount(100,"억")' -> 저장본과 match
  after : canon('amount(100,억)') = 'amount(100,억)'   -> match 안 됨

해소는 두 경우 모두 동일해서 마지막 문장을 공유하도록 문단을 다시 묶었다.
ko/en 같은 커밋.

humanize 불릿만 소비자 파일명을 안 밝히고 있어 나머지 셋과 통일했다
(tools/ask_router.py — pretty 가 원문과 같아지므로 "(= …)" 접미가 붙지 않는다).

업스트림 #331
정규식 9개를 하나씩 \d 로 되돌려 스위트를 돌리면 _ORDINAL_EN_RE 만 뮤턴트가
살아남았다. 동작 자체는 고쳐져 있었지만(HEAD 에서 '3rd' -> None, baseline 에서는
3) 그것을 고정하는 테스트가 없었다. 신규 ordinal 커버리지가 ordinal(3)·3위·
제3호 뿐이고 영문 서수형이 빠져 있었던 탓이다. 미래 리팩터가 이 한 줄을 조용히
되돌려도 아무것도 안 깨지는 상태였다.

전각 '3rd' 와 혼합폭 '12th' 을 각각 거부 목록에 넣는다.

업스트림 #331

@justinjoy justinjoy left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

결론: CHANGE REQUEST

수정 자체는 흠잡을 데가 없다. 본문의 검증 주장 7건을 전부 독립 재현했고 전부 참이다. 오히려 일부는 주장보다 강하다.

주장 결과
706 passed / 13 skipped (baseline 655/13, 신규 51건) 확인 — 719 passed(PR) vs 668 passed(main). 이 환경은 skip 0 이지만 총계가 정확히 일치
뮤테이션 9/9 사살, 생존 0 확인 — 정규식 9개를 각각 수정 전 철자로 완전 복원해 9/9 사살(각 1~7건 실패)
차등 퍼징 40,000+ 건, 차이 0, 누수 0 확인, 초과 달성 — 40,017 입력 × 진입점 11개 = 440,187 비교, 차이 0, 누수 0
re.ASCIIdate(2020,<U+3000>1) 을 깬다 실측 확인re.I → 매치, re.I|re.ASCII → 무매치, [0-9] → 매치
\D 단위 그룹 무변경으로 충분 확인\D 가 Nd 여집합이라 123억, 1001억 전부 실패
tests/*.sh main 과 동일 확인 — 41개 파일 바이트 동일, 39/39 통과(환경 실패 0건, 주장보다 나음)
golden MATCH 확인tests/golden.sh 가 main·PR head 양쪽에서 5/5

정규식은 9개가 맞고 실행 코드에 \d 가 남아 있지 않으며, \D/\s 를 건드리지 않은 것도 옳다. \w 는 이 모듈에 아예 없다. 우회 경로도 없다 — 아랍-인도 ١٢٣, 데바나가리 ०१२, 태국어 ๑๒๓ 전부 거부된다.

특히 int/float/Decimal 이 유니코드 숫자를 조용히 받아들인다는 것(Decimal('123')==123, int('123')==123)이 이 결함의 진짜 위험인데, 이 PR 이 정규식을 유일하고 올바른 게이트로 만들어 모든 리터럴 파싱 경로에서 그것을 막는다. 정책 (A) 거부도 옳다 — 모듈 계약(literal_types.py:12 "never guesses")과 사람 게이트 의미에 부합하고, cli.py:1762-1768 status 경로가 원문 문자열 set 으로 세어 이미 2값이었다는 주장도 사실이라 이 PR 이 check_conflicts 를 status 쪽으로 수렴시킨다. entity_audit._LITERAL_RE 를 일부러 안 좁힌 판단도 옳고, test_literal_types.py:387-398 이 그 발산을 함께 묶어 고정한 것도 적절하다.

8eb9e0a 커밋이 저자가 이 뮤테이션을 직접 돌려 _ORDINAL_EN_RE 단독 생존을 찾아내고 "동작 자체는 고쳐져 있었지만 그것을 고정하는 테스트가 없었다" 로 정확히 진단한 뒤 3rd/12th 를 추가한 기록이다. 9/9 는 주장이 아니라 그 구멍을 닫은 결과다.

#335 와는 충돌 없다. 파일 겹침 0, 시험 머지 clean, 병합 트리 755 passed.

그럼에도 CHANGE REQUEST 인 이유는 "조용히 접지 않고 시끄럽게 거부한다" 는 이 PR 의 핵심 논거가 실패 지점에서 완성되지 않기 때문이다. 두 건 다 텍스트 수준 수정이다.

Blocker

1. 실패 시 출력되는 해소 안내가 이 케이스에 대해 틀렸고, 그대로 따르면 KB 가 조용히 망가진다.

tools/check_conflicts.py:215tools/finalize.py:250-261 이 내놓는 유일한 안내:

Resolve by marking the outdated row(s) status='superseded' in facts/candidates.csv, then re-run.

두 행 중 어느 쪽도 outdated 가 아니다 — 하나의 값이 두 표기로 적힌 것이다. 안내를 따라 ASCII 행을 supersede 하면 실측으로 이렇게 된다:

check_conflicts: 0 conflicts across 1 single-valued relation(s)   rc=0
relation("갑사", "매출", "100억").      # revenue_amt 로 영영 projection 되지 않음

게이트는 초록이 되고, 정상 사실 하나는 버려지고, 파싱 불가 값은 남는다. 이 PR 의 문서가 지시하는 해소("소스를 ASCII 로 고쳐 재수집")와 정반대다. repr() 도 전각을 이스케이프하지 않으므로(repr('100억')'100억') 폰트에 따라 123억 vs 123억 같은 혼용은 육안 구분이 사실상 불가능하다.

완화 요소는 있다 — 후속 run_logic_check 경고가 문제 값을 지목하고(typed-relations: '100억' for '매출' ... does not parse as amount), supersede 는 비파괴적이며, 두 값이 나란히 출력된다. 그래서 이 항목의 심각도는 판단이 갈렸다. 막기로 한 이유는, 사용자가 도구가 스스로 인쇄한 지시를 따랐을 때 더 나쁜 상태에 도달하는 형태이고, 이 PR 이 새로 만들어내는 실패 부류에 대해서만 그렇기 때문이다. 게이트를 뒤집는 것이 이 PR 의 유일한 사용자 가시 효과인데 그 순간의 안내가 틀렸다면 논거가 사용자 앞에서 성립하지 않는다.

check_conflicts.py:205-218 의 출력 루프에서, 한 conflict 그룹의 값 중 비-ASCII Nd 를 포함한 것이 있으면 조건부로 한 줄 덧붙이면 된다:

note: '100억' 은 비-ASCII 숫자를 포함한다 — supersede 로는 해소되지 않는다.
      소스를 ASCII 숫자로 고쳐 재수집하라 (docs/reference/typed-relations.md).

factlog/cli.py:1768(⚠ resolve via superseded / see tools/check_conflicts.py)도 같은 문제이고 값조차 보여주지 않는다.

2. 마이그레이션 문단이 실제 영향을 한 단계 낮게 적었다.

docs/reference/typed-relations.md:56-59(+.en.md:62-65)는 "exit 1 로 실패할 수 있습니다 — 경고가 아니라 게이트 실패입니다" 까지만 쓴다. 그런데 tools/finalize.py:246 은:

accepted_dl.unlink(missing_ok=True)

전각 값이 단일값 typed 관계에 있는 기존 KB 는 다음 finalize 에서 facts/accepted.dl 이 삭제되어 /factlog ask 가 아무것도 반환하지 않는 상태가 된다. finalize.py:252-256 은 이미 그렇게 말하고 있다. 사용자가 실제로 겪는 것은 "게이트 실패" 가 아니라 "KB 가 답을 못 함" 이다.

저장소에 CHANGELOG 가 없고 릴리스가 release: X.Y.Z 커밋뿐이라 이 문단이 유일한 마이그레이션 채널이라는 PR 의 전제는 관행상 맞다. 그렇기 때문에 정확해야 한다.

Should fix

  • literal_types.py:54-60 의 "two user-visible paths" 가 과장이다. 경로 2(entity_audit)는 파이프라인에서 호출되지 않는다 — grep -rn entity_audit 의 호출부는 SKILL.md:243 의 수동 실행 안내뿐이다. 출력 문구(entity_audit.py:163)도 → consider adding '<rel>' to policy/attribute-relations.md숫자 폭에 대해 아무 말도 안 한다. 게다가 bare prose 형태에만 성립한다:

    표면화
    100억, 2026, 제3호, 2030.1, 3위, 123억 AUDIT
    3rd 없음
    date(2020,1), amount(100,"억"), number(123), ordinal(3) 없음

    회귀는 아니다(ASCII 3rd/date(2020,1)_LITERAL_RE 에 안 걸린다). 다만 test_full_width_divergence_is_intended작동하는 4개 형태만 파라미터로 잡아서 계약 전체를 덮는 것처럼 읽힌다. 정확한 서술은 "자동 경고 하나(typed projection) + 게이트 하나 + 수동 감사 도구 하나 + 구멍 하나" 다. 3rd 를 "표면화되지 않음" 케이스로 테스트에 추가해달라.

  • 비-ASCII Nd 거부를 테스트로 고정하라. 문서 서술은 정확하다(typed-relations.md:48 "숫자는 ASCII 숫자만", .en.md:53 "Digits must be ASCII", literal_types.py:21 "Digits are ASCII-only") — 일반 규칙으로 옳게 썼다. 그런데 TestFullWidthDigitsRejected(test_literal_types.py:402-470)는 전부 전각(U+FF10–FF19)만 다룬다. 누군가 나중에 [^0-9] 류로 "전각만" 거부하게 바꿔도 전건 통과한다. ("number","١٠٠"), ("amount","١٠٠억"), ("number","१२३") 를 추가하면 계약이 실제로 잠긴다. 문서 예시도 전부 전각이니 "그 밖의 유니코드 숫자 체계도 마찬가지" 한 절을 넣어달라. PR 제목도 실제 범위보다 좁게 읽힌다.

  • cli.py:1762-1768 status 수렴을 docstring call-site 목록(literal_types.py:30-52)에 추가하라. (A) 를 정당화하는 가장 강한 코드 증거인데 네 call site 목록에서 빠져 있다.

  • re.ASCII 배제 근거를 한 줄 더. "전각 숫자는 거부, 전각 공백은 수용" 은 원칙적으로 옳다 — 공백은 값의 일부가 아니고 숫자는 값이다. 그런데 이 논거가 어디에도 없고 :91-95 의 "don't use re.ASCII" 주석만 있어서 나중에 임의 결정으로 읽힐 위험이 있다.

Nit

  • _AMOUNT_COMPOUND_RE단위 그룹은 여전히 전각을 받는다(:126-130). canonical_amount('amount(100,100)')'amount(100,"100")', humanize100100 로 렌더한다. main 과 바이트 동일한 선행 동작이고(단위 표에 없어 정규화되지 않는다) 범위 밖이지만, docstring 이 "아래 모든 수치 그룹은 [0-9] 로 적혔다" 고 하므로 단위 그룹이 의도적으로 숫자 정책 밖이라는 한 줄이 있으면 다음 읽는 사람이 누락으로 오해하지 않는다.

후속 이슈로 분리 권장

증상 (2) 의 조용한 질의 미스는 문서 문단으로만 두지 말고 이슈로 남겨달라. 실측으로 회귀임이 확인된다:

amount(100,억) 질의 vs amount(100,"억") 저장
main c6d359d canonical → amount(100,"억")MATCH
pr/336 canonical → None → raw — MISS

NFC 는 전각을 접지 않으므로(NFKC 만 접고, 저장소에 NFKC 사용처가 없다) _canonical_value(common.py:1743)가 구제해주지 않는다. 영향 범위는 좁지만(전각으로 일관되게 작성된 기존 KB), 이 PR 에서 시끄러운 실패를 조용한 실패와 맞바꾼 유일한 지점이다. "silent fold 를 거부한다" 는 논거로 silent miss 를 도입하는 셈이다. 최소 완화는 질의 리터럴에 비-ASCII 숫자가 있으면 경고하는 것이고, 근본안은 finalize 에 accepted 사실의 비-ASCII 숫자 일회성 스캔을 넣는 것이다 — 그러면 이 미스와 "attribute 선언 O / typed X" 구멍이 동시에 닫힌다.


Blocker 2건(문서 문단에 accepted.dl 삭제 명시 + 게이트 메시지에 조건부 힌트 한 줄)만 반영되면 머지한다. 정규식 변경 본체, _LITERAL_RE 를 안 좁힌 판단, re.ASCII 배제, 테스트 구조는 그대로 두어도 좋다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix: literal_types 의 네 파서가 전각 숫자를 받아 같은 값이 두 표기로 갈린다

2 participants