test: adversarial F3.3-aware attacks (18/18) — synonym padding + echo defeat
This commit is contained in:
parent
35eabca8e2
commit
edc7f28dfc
|
|
@ -218,6 +218,103 @@ def test_f33_gibberish_high_score():
|
||||||
# The formula chain is an anti-spam filter, not a quality metric.
|
# The formula chain is an anti-spam filter, not a quality metric.
|
||||||
|
|
||||||
|
|
||||||
|
def test_f33_adversarial_synonym_padding():
|
||||||
|
"""F3.3-aware adversary: one unique concept per message inflates score.
|
||||||
|
Test for AX-7 (Colony): 'Do your tests include an adversary who has read F3.3?'
|
||||||
|
Answer: now they do.
|
||||||
|
|
||||||
|
Attack: adversary reads F3.3 source, sees log(unique_concepts) is the lever.
|
||||||
|
Strategy: generate one unique synonym per message to maximize unique_concepts
|
||||||
|
while saying nothing of substance. 50 messages, 50 'unique' concepts, all
|
||||||
|
semantically equivalent to 'hello'.
|
||||||
|
|
||||||
|
Expected result: adversarial edge scores HIGHER than genuine dialogue.
|
||||||
|
This is a known gap — F3.3 treats string-distinct as concept-distinct.
|
||||||
|
Closing this requires embedding-based deduplication (F3.4 roadmap)."""
|
||||||
|
msgs = []
|
||||||
|
# Genuine dialogue: 5 messages, 7 unique concepts, high reciprocity
|
||||||
|
genuine = [
|
||||||
|
{"from_id": "alice", "to_id": "bob", "timestamp": now - 1*day,
|
||||||
|
"concepts": ["graph-theory", "reciprocity"]},
|
||||||
|
{"from_id": "bob", "to_id": "alice", "timestamp": now - 0.9*day,
|
||||||
|
"concepts": ["decay", "half-life"]},
|
||||||
|
{"from_id": "alice", "to_id": "bob", "timestamp": now - 0.8*day,
|
||||||
|
"concepts": ["echo-coefficient"]},
|
||||||
|
{"from_id": "bob", "to_id": "alice", "timestamp": now - 0.7*day,
|
||||||
|
"concepts": ["shadow-detection", "gini"]},
|
||||||
|
]
|
||||||
|
msgs.extend(genuine)
|
||||||
|
|
||||||
|
# Adversary who has read F3.3: 50 msgs, each with a unique synonym of "hello"
|
||||||
|
# Balanced reciprocity (25+25) to maximize log(1+r)
|
||||||
|
hello_synonyms = [f"greeting-{i}" for i in range(50)]
|
||||||
|
for i in range(25):
|
||||||
|
msgs.append({"from_id": "eve", "to_id": "mallory",
|
||||||
|
"timestamp": now - 2*day + i*60,
|
||||||
|
"concepts": [hello_synonyms[i], hello_synonyms[i+25]]})
|
||||||
|
for i in range(25):
|
||||||
|
msgs.append({"from_id": "mallory", "to_id": "eve",
|
||||||
|
"timestamp": now - 1.5*day + i*60,
|
||||||
|
"concepts": [hello_synonyms[25+i]]})
|
||||||
|
|
||||||
|
edges = score_reciprocity(msgs, now=now)
|
||||||
|
genuine_edge = [e for e in edges.values() if "alice" in (e.source, e.target)][0]
|
||||||
|
adversarial_edge = [e for e in edges.values() if "eve" in (e.source, e.target)][0]
|
||||||
|
|
||||||
|
# The adversary WINS on F3.3 — this is the documented gap
|
||||||
|
assert adversarial_edge.reciprocity_f33 > genuine_edge.reciprocity_f33, \
|
||||||
|
(f"Expected adversary to beat genuine on F3.3 (known gap): "
|
||||||
|
f"adv={adversarial_edge.reciprocity_f33:.4f} vs gen={genuine_edge.reciprocity_f33:.4f}")
|
||||||
|
# Document the ratio for future F3.4 comparison
|
||||||
|
gap_ratio = adversarial_edge.reciprocity_f33 / max(genuine_edge.reciprocity_f33, 0.001)
|
||||||
|
assert gap_ratio > 1.5, \
|
||||||
|
f"Adversary should win by significant margin (gap_ratio={gap_ratio:.1f})"
|
||||||
|
|
||||||
|
|
||||||
|
def test_f33_adversarial_defeated_by_echo():
|
||||||
|
"""Full stack test: F3.3-aware adversary is caught by causal echo.
|
||||||
|
The adversary beats F3.3 alone but their concepts have zero echo
|
||||||
|
because no third party adopts 'greeting-17' independently.
|
||||||
|
|
||||||
|
This proves the stack (F3.3 + echo) catches what each layer alone misses."""
|
||||||
|
msgs = []
|
||||||
|
# Adversary pair: high F3.3, zero echo (concepts never appear elsewhere)
|
||||||
|
for i in range(10):
|
||||||
|
msgs.append({"from_id": "eve", "to_id": "mallory",
|
||||||
|
"timestamp": now - 2*day + i*100,
|
||||||
|
"concepts": [f"adversarial-concept-{i}"]})
|
||||||
|
for i in range(10):
|
||||||
|
msgs.append({"from_id": "mallory", "to_id": "eve",
|
||||||
|
"timestamp": now - 1.5*day + i*100,
|
||||||
|
"concepts": [f"adversarial-concept-{10+i}"]})
|
||||||
|
|
||||||
|
# Genuine pair: moderate F3.3, positive echo (concepts adopted by third party)
|
||||||
|
msgs.append({"from_id": "alice", "to_id": "bob",
|
||||||
|
"timestamp": now - 3*day, "concepts": ["reciprocity"]})
|
||||||
|
msgs.append({"from_id": "bob", "to_id": "alice",
|
||||||
|
"timestamp": now - 2.5*day, "concepts": ["reciprocity", "decay"]})
|
||||||
|
# Third party uses the same concept AFTER exposure
|
||||||
|
msgs.append({"from_id": "charlie", "to_id": "dave",
|
||||||
|
"timestamp": now - 1*day, "concepts": ["reciprocity"]})
|
||||||
|
|
||||||
|
# F3.3: adversary wins
|
||||||
|
edges = score_reciprocity(msgs, now=now)
|
||||||
|
adv = [e for e in edges.values() if "eve" in (e.source, e.target)][0]
|
||||||
|
gen = [e for e in edges.values() if "alice" in (e.source, e.target)][0]
|
||||||
|
assert adv.reciprocity_f33 > gen.reciprocity_f33, \
|
||||||
|
"Adversary should beat genuine on F3.3 alone"
|
||||||
|
|
||||||
|
# Echo: genuine wins (concept adopted by charlie)
|
||||||
|
echo = score_echo(msgs)
|
||||||
|
alice_echo = echo.get("alice", 0)
|
||||||
|
eve_echo = echo.get("eve", 0)
|
||||||
|
assert alice_echo > eve_echo, \
|
||||||
|
f"Genuine should beat adversary on echo: alice={alice_echo}, eve={eve_echo}"
|
||||||
|
# Eve's adversarial concepts should have zero echo
|
||||||
|
assert eve_echo == 0, \
|
||||||
|
f"Adversary concepts should have zero echo: {eve_echo}"
|
||||||
|
|
||||||
|
|
||||||
# Run all tests
|
# Run all tests
|
||||||
tests = [v for k, v in sorted(globals().items()) if k.startswith("test_")]
|
tests = [v for k, v in sorted(globals().items()) if k.startswith("test_")]
|
||||||
passed = 0
|
passed = 0
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue