|
12 | 12 |
|
13 | 13 | from permea_core.diagnose import diagnose |
14 | 14 | from permea_core.eval.run import EvalRun, MetricRow |
15 | | -from permea_explain import GuardrailViolation, guardrails, narrate, source |
| 15 | +from permea_explain import GuardrailViolation, guardrails, narrate, prompt, source |
16 | 16 | from permea_explain.extract import extract |
17 | 17 | from permea_explain.providers.base import Provider, ProviderResponse |
18 | 18 | from permea_explain.violations import NUMERIC_UNTRACED |
@@ -373,6 +373,38 @@ def test_pure_digit_and_decimal_tokenization_unchanged(): |
373 | 373 | assert guardrails.numbers_in_narrative("PERMEA-W101 이 발화, 정확도 0.87") == ["0.87"] |
374 | 374 |
|
375 | 375 |
|
| 376 | +# -------------------------------------------------------------------------------------- |
| 377 | +# A-priori sign convention (#0034) |
| 378 | +# -------------------------------------------------------------------------------------- |
| 379 | +# SYSTEM_PROMPT carries a standing instruction to always write signed fields (deltas, CI |
| 380 | +# bounds) WITH their sign -- stated ONCE, before any generation, as defence in depth mirroring |
| 381 | +# the guardrail's sign-aware token matching. It is NOT a retry after a violation (the verifier |
| 382 | +# must never become a training signal). The instruction itself must carry no numeric literal: |
| 383 | +# commit b24737a exists because literals injected into prompt-visible text came back in |
| 384 | +# narrations and tripped the number gate, so the added sentence must tokenize to [] under the |
| 385 | +# SAME extractor the gate uses at runtime. |
| 386 | +_SIGN_CONVENTION = ( |
| 387 | + "부호가 있는 숫자 필드(델타, 신뢰구간 경계 등)는 항상 그 부호를 " |
| 388 | + "붙여 그대로 쓰십시오. 방향을 동사로 나타내더라도 부호를 뗀 크기(절댓값)만 적는 " |
| 389 | + "것은 허용되지 않습니다. 음수 필드는 반드시 음의 부호와 함께 기재하십시오." |
| 390 | +) |
| 391 | + |
| 392 | + |
| 393 | +def test_sign_convention_present_in_system_prompt(): |
| 394 | + """The convention is a-priori: it lives in the standing prompt, sent before generation. |
| 395 | +
|
| 396 | + Matched whitespace-insensitively because SYSTEM_PROMPT hard-wraps its rules across |
| 397 | + indented lines, so the sentence spans several ``\\n `` breaks in the source. |
| 398 | + """ |
| 399 | + collapse = lambda s: re.sub(r"\s+", " ", s).strip() |
| 400 | + assert collapse(_SIGN_CONVENTION) in collapse(prompt.SYSTEM_PROMPT) |
| 401 | + |
| 402 | + |
| 403 | +def test_sign_convention_carries_no_numeric_literal(): |
| 404 | + """A faithful copy of the instruction must not itself fail the provenance gate (b24737a).""" |
| 405 | + assert guardrails._standalone_number_tokens(_SIGN_CONVENTION) == [] |
| 406 | + |
| 407 | + |
376 | 408 | def test_extract_is_available_alongside_narrate(): |
377 | 409 | """Both task layers share one Provider and one verification discipline (#0023).""" |
378 | 410 | import inspect |
|
0 commit comments