Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions scripts/check_paradigm_shift.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@
to a negated clause followed by an It/They/You + to-be sentence;
looser period-split parallelism without negation stays LLM-only)

E — "<Noun> is not X. <Noun> is Y" (restated subject, with or without
the/this/that; the same words must open both sentences)

F — "is not just X. It may be / can become Y" (period-split with a
modal; lower precision, its own label so Ralph's LLM decides)

D — "Not X but Y" (Aristotelian inversion; informational)
"""
from __future__ import annotations
Expand Down Expand Up @@ -87,6 +93,35 @@
"antithesis-restated-subject",
"high",
),
(
# Restated bare-noun subject at the start of a sentence: "Least
# privilege is not just fewer actions. Least privilege is smaller
# failure domains." Same backreference guard as above, for a subject
# of one to three words without the/this/that (2026-10-23 miss).
re.compile(
rf"(?im)(?:^|(?<=[.!?] ))(?:[-*]\s+)?"
rf"([a-z]\w*(?:\s+\w+){{0,2}})\s+(?:is|are|was|were)"
rf"(?:\s+not|n{_AP}?t)\s+[^.;:\n]{{1,80}}\.\s+"
rf"\1\s+(?:is|are|was|were)\b"
),
"antithesis-restated-noun",
"high",
),
(
# Period-split with a modal or "become" in the second sentence:
# "... is not just a cluster detail. It can become a lateral movement
# primitive." Requires "not just" ("The build is not finished. It can
# be retried." is ordinary prose). Lower precision than the forms
# above (a regex cannot tell whether "It" refers to the rejected
# subject), so the label is kept separate and Ralph's LLM pass decides.
re.compile(
rf"(?i)\b(?:is|are|was|were)(?:\s+not|n{_AP}?t)\s+just\s+"
rf"[^.;:\n]{{1,80}}\.\s+(?:It|They|This)\s+"
rf"(?:(?:may|might|can|could)\s+(?:also\s+)?(?:be|become)|becomes?)\b"
),
"antithesis-period-split-modal",
"high",
),
(
re.compile(
r"(?i)\bnot\s+[a-z]\w+(?:ing|ed|s|ly)?[,.\s]+but\s+[a-z]\w+"
Expand Down
62 changes: 62 additions & 0 deletions tests/test_check_paradigm_shift.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,68 @@ def test_negative_no_negation(self):
self.assertNotIn("antithesis-restated-subject", labels_for(text))


class TestRestatedNoun(unittest.TestCase):
"""'<Noun> is not X. <Noun> is Y.' without the/this/that (2026-10-23 miss)."""

def test_bare_noun_not_just(self):
# iam-blast-radius-architecture-problem L28
text = "Least privilege is not just fewer actions. Least privilege is smaller failure domains."
self.assertIn("antithesis-restated-noun", labels_for(text))

def test_mid_paragraph_and_contraction(self):
text = "We tried. Defense in depth isn’t a product. Defense in depth is a habit."
self.assertIn("antithesis-restated-noun", labels_for(text))

def test_bullet(self):
text = "- Logging is not a control. Logging is evidence."
self.assertIn("antithesis-restated-noun", labels_for(text))

def test_determiner_form_reported_once(self):
text = "The goal is not a prettier IAM policy. The goal is a smaller blast radius."
self.assertEqual(labels_for(text), ["antithesis-restated-subject"])

def test_negative_different_subject(self):
text = "Logging is not enabled. Monitoring is on."
self.assertNotIn("antithesis-restated-noun", labels_for(text))

def test_negative_subject_mid_sentence(self):
# the repeated words do not open the first sentence
text = "We know least privilege is not free. Least privilege is still worth it."
self.assertNotIn("antithesis-restated-noun", labels_for(text))


class TestPeriodSplitModal(unittest.TestCase):
"""'... is not just X. It may be / can become Y.' (2026-10-23 miss)."""

def test_it_may_be(self):
# iam-blast-radius-architecture-problem L32
text = (
"A workload identity that can read secrets is not just an "
"application permission. It may be a persistence path."
)
self.assertIn("antithesis-period-split-modal", labels_for(text))

def test_it_can_become(self):
# iam-blast-radius-architecture-problem L33
text = (
"A service account that can exec into pods is not just a cluster "
"detail. It can become a lateral movement primitive."
)
self.assertIn("antithesis-period-split-modal", labels_for(text))

def test_it_becomes(self):
text = "The runner is not just a build box. It becomes a deploy credential."
self.assertIn("antithesis-period-split-modal", labels_for(text))

def test_negative_without_just(self):
text = "The build is not finished. It can be retried after the fix."
self.assertEqual(labels_for(text), [])

def test_negative_will_be(self):
text = "The report is not just late. It will be rewritten."
self.assertNotIn("antithesis-period-split-modal", labels_for(text))


class TestExistingPatternsStillWork(unittest.TestCase):
def test_pronoun_period_split_iam_blast(self):
# iam-blast-radius-github-action L16 (2026-10-23): flagged here, but the
Expand Down
Loading