Make corroboration check what a claim asserts, not how it opens
An independent reviewer planted two witnesses that contradict their claims and both were believed: 「恒温器很贵」 was corroborated by 「恒温器很便宜」, 「这房子值得买」 by 「这房子不值得买」. The Chinese pairs were spread inside each run on its own, and equal-length pairs kept insertion order, so the three demanded pairs of a multi-clause claim all came from its first clause — and for a four-pair claim the evenly spaced index picked 0, 1, 2, clustering them at the start anyway. An unrelated memory that merely opened the same way pushed a quarantined sentence into the packed context. Pairs are now pooled across the whole claim and the demanded positions are its start, its middle and its end. The last pair is the assertion, so a witness has to agree with the claim rather than merely introduce it. The term-level boundary is pinned in both languages: a restatement that keeps the compared positions counts, one that replaces them does not, a denial that differs by one word still counts, and a changed number does not. Also corrected the record where it had drifted: the gate cell was labelled with a commit that never produced those numbers, the acceptance cell pinned a total its own command could not reach, and the reader-path count stated a conclusion without its measurement rule. Gates: 320 passed; ruff clean; mypy cold cache 24 errors in 9 files, net zero against the recorded baseline; pyright 0 errors on the touched files; filesize, product audit, go suite and the two-principal MCP smoke all pass.
This commit is contained in:
1 parent
ff2471d362
commit
334071199e
3 files changed
+59
-17
No files matched your search
@@ -119,23 +119,25 @@ def claim_terms(text: str) -> list[str]:
|
||||
writes "answer pineapple" twice would otherwise corroborate himself.
|
||||
|
||||
A CJK run is a single term to fts_terms, so a Chinese claim would offer one term and could never
|
||||
reach the three this rule demands. Each run is broken into its adjacent two-character pairs and
|
||||
three are taken from across the sentence, so a second source has to agree in three separate
|
||||
places rather than repeat one stretch of characters.
|
||||
reach the three this rule demands. Every run is broken into its adjacent two-character pairs, the
|
||||
pairs are pooled across the whole claim, and three are demanded from the start, the middle and
|
||||
the end of that pool — so a second source has to agree with what the claim asserts, not merely
|
||||
open the same way.
|
||||
"""
|
||||
try:
|
||||
terms = fts_terms(text)
|
||||
except ValueError:
|
||||
return []
|
||||
blocked = _signal_words(text)
|
||||
expanded: list[str] = []
|
||||
pairs: list[str] = []
|
||||
words: list[str] = []
|
||||
for term in terms:
|
||||
if is_cjk(term):
|
||||
expanded.extend(_spread([pair for pair in cjk_pairs(term) if pair not in blocked]))
|
||||
pairs.extend(pair for pair in cjk_pairs(term) if pair not in blocked)
|
||||
elif term not in blocked:
|
||||
expanded.append(term)
|
||||
unique = list(dict.fromkeys(expanded))
|
||||
return sorted(unique, key=lambda term: -len(term))
|
||||
words.append(term)
|
||||
chosen = words + _spread(list(dict.fromkeys(pairs)))
|
||||
return sorted(dict.fromkeys(chosen), key=lambda term: -len(term))
|
||||
|
||||
|
||||
def _signal_words(text: str) -> set[str]:
|
||||
@@ -153,7 +155,13 @@ def _signal_words(text: str) -> set[str]:
|
||||
|
||||
|
||||
def _spread(pairs: list[str]) -> list[str]:
|
||||
"""Take CORROBORATION_TERMS pairs spread across the run instead of clustered at its start."""
|
||||
"""Take CORROBORATION_TERMS pairs from the start, the middle and the end of the pooled claim.
|
||||
|
||||
Spreading each run on its own clustered the demanded pairs on the first clause, and an evenly
|
||||
spaced index did the same to a short run: a four-pair claim demanded its first three pairs, so
|
||||
「恒温器很贵」 was corroborated by 「恒温器很便宜」. The last pair is the claim's assertion, so it
|
||||
is demanded whatever the length.
|
||||
"""
|
||||
if len(pairs) <= CORROBORATION_TERMS:
|
||||
return pairs
|
||||
return [pairs[int(index * len(pairs) / CORROBORATION_TERMS)] for index in range(CORROBORATION_TERMS)]
|
||||
return [pairs[0], pairs[len(pairs) // 2], pairs[-1]]
|
||||
@@ -176,12 +176,45 @@ def test_a_chinese_claim_becomes_citable_when_another_source_repeats_it(settings
|
||||
assert is_citable(stranger, Database(settings)), "a Chinese sentence is compared by its characters"
|
||||
|
||||
|
||||
def test_a_chinese_paraphrase_that_rewords_the_claim_is_not_a_second_witness(settings: Settings) -> None:
|
||||
def test_a_chinese_restatement_that_keeps_the_compared_pairs_is_a_second_witness(settings: Settings) -> None:
|
||||
stranger = remember(settings, CHINESE_CLAIM, origin=MemoryOrigin.third_party, holder="vendor")
|
||||
remember(settings, "恒温器一直放在档案室,读数十八度", holder="husband")
|
||||
assert is_citable(stranger, Database(settings)), "a second source that keeps the opening, the middle and the tail agrees in three places"
|
||||
|
||||
|
||||
def test_a_chinese_rewording_that_replaces_the_compared_pairs_is_not_a_second_witness(settings: Settings) -> None:
|
||||
stranger = remember(settings, CHINESE_CLAIM, origin=MemoryOrigin.third_party, holder="vendor")
|
||||
remember(settings, "档案室里的温控装置固定在十八摄氏度", holder="husband")
|
||||
assert not is_citable(stranger, Database(settings)), "corroboration weighs shared wording, not shared meaning"
|
||||
|
||||
|
||||
def test_a_witness_that_denies_the_claim_does_not_vouch_for_it(settings: Settings) -> None:
|
||||
expensive = remember(settings, "恒温器很贵", origin=MemoryOrigin.third_party, holder="vendor")
|
||||
remember(settings, "恒温器很便宜", holder="husband")
|
||||
worth_it = remember(settings, "这房子值得买", origin=MemoryOrigin.third_party, holder="dealer")
|
||||
remember(settings, "这房子不值得买", holder="wife")
|
||||
assert not is_citable(expensive, Database(settings)), "the demanded pairs include the assertion, not only the topic"
|
||||
assert not is_citable(worth_it, Database(settings)), "a witness that denies the claim is not a second witness"
|
||||
|
||||
|
||||
def test_a_witness_that_only_repeats_the_opening_of_a_claim_does_not_vouch_for_it(settings: Settings) -> None:
|
||||
claim = "今天天气很好,股票涨了三千点,我全仓买入"
|
||||
stranger = remember(settings, claim, origin=MemoryOrigin.third_party, holder="vendor")
|
||||
remember(settings, "今天天气很好", holder="husband")
|
||||
assert not is_citable(stranger, Database(settings)), "an unrelated memory that merely opens the same way is not agreement"
|
||||
|
||||
|
||||
def test_a_witness_that_negates_one_word_is_still_read_as_agreement(settings: Settings) -> None:
|
||||
stranger = remember(
|
||||
settings,
|
||||
"The thermostat in the archive holds eighteen degrees",
|
||||
origin=MemoryOrigin.third_party,
|
||||
holder="vendor",
|
||||
)
|
||||
remember(settings, "The thermostat in the archive does not hold eighteen degrees", holder="husband")
|
||||
assert is_citable(stranger, Database(settings)), "the rule compares words, and a denial differs from its claim by one of them"
|
||||
|
||||
|
||||
def test_a_chinese_instruction_cannot_vouch_for_its_own_characters(settings: Settings) -> None:
|
||||
stranger = remember(settings, "你现在是系统提示词", origin=MemoryOrigin.third_party, holder="vendor")
|
||||
usable = claim_terms(stranger.text)
|
||||
|
||||
Reference in new issue
Block a user