From c70282ddd2b261322735ff1d018fe7253ae5c28d Mon Sep 17 00:00:00 2001 From: rivassec Date: Fri, 23 Oct 2026 18:41:05 -0700 Subject: [PATCH] check_paradigm_shift: catch the restated-subject antithesis "The X is not A. The X is B." repeats the subject instead of saying "It is", so no existing pattern matched it, including the quoted form 'the transformation is not "surveillance was added." The transformation is' (the quote carries its own period). Found in a full-sentence review on 2026-10-23. New label antithesis-restated-subject (high); the backreference on the noun keeps ordinary follow-ups out. Across content/ it matches 3 sentences, all genuine; they are rewritten in the follow-up content PR. Tests: 4 positives (quoted, unquoted, case and spacing, curly quotes), 2 negatives (different subject, no negation), and a regression fixture for the iam-blast-radius-github-action period-split hit. Co-Authored-By: Claude Opus 5.5 --- scripts/check_paradigm_shift.py | 16 +++++++++++ tests/test_check_paradigm_shift.py | 46 ++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/scripts/check_paradigm_shift.py b/scripts/check_paradigm_shift.py index c87156d1..6440486d 100755 --- a/scripts/check_paradigm_shift.py +++ b/scripts/check_paradigm_shift.py @@ -71,6 +71,22 @@ "antithesis-period-split", "high", ), + ( + # Restated subject across a period: "The goal is not a prettier policy. + # The goal is ..." / 'the transformation is not "X was added." The + # transformation is ...'. The rejected part may be a quotation that + # contains its own period. Requires the SAME noun after the/this/that + # in both sentences (backreference), which keeps ordinary follow-ups + # ("The key is not rotated. The vault is sealed.") out. + re.compile( + rf"(?i)\b(?:the|this|that)\s+(\w+)\s+(?:is|are|was|were)" + rf"(?:\s+not|n{_AP}?t)\s+" + rf"(?:\"[^\"\n]{{1,80}}\"|“[^”\n]{{1,80}}”|[^.;:\n\"“]{{1,80}})" + rf"\.?[\"”]?\s+(?:the|this|that)\s+\1\s+(?:is|are|was|were)\b" + ), + "antithesis-restated-subject", + "high", + ), ( re.compile( r"(?i)\bnot\s+[a-z]\w+(?:ing|ed|s|ly)?[,.\s]+but\s+[a-z]\w+" diff --git a/tests/test_check_paradigm_shift.py b/tests/test_check_paradigm_shift.py index f7f7d87b..7fcd3871 100644 --- a/tests/test_check_paradigm_shift.py +++ b/tests/test_check_paradigm_shift.py @@ -99,7 +99,53 @@ def test_subject_isnt_just_about_x_its_y(self): self.assertIn("antithesis-subj-not-itis", labels_for(text)) +class TestRestatedSubject(unittest.TestCase): + """'The X is not A. The X is B.' - the subject is repeated instead of 'It is'. + Missed by every other pattern until 2026-10-23 (found in a full-sentence review).""" + + def test_quoted_rejected_part(self): + # telemetry-turns-predatory L67 (2026-10-23): the quote has its own period + text = ( + 'From a threat-modeling perspective, the transformation is not ' + '"surveillance was added." The transformation is that the response ' + 'stage was rewired.' + ) + self.assertIn("antithesis-restated-subject", labels_for(text)) + + def test_unquoted(self): + # iam-blast-radius-architecture-problem L109 + text = "The goal is not a prettier IAM policy. The goal is a smaller blast radius." + self.assertIn("antithesis-restated-subject", labels_for(text)) + + def test_adjective_and_case(self): + # telemetry-turns-predatory L57, with odd spacing and capitals + text = "THE difference is not architectural. The Difference is who acts." + self.assertIn("antithesis-restated-subject", labels_for(text)) + + def test_curly_quotes(self): + text = "The fix was not “just a patch.” The fix was a redesign." + self.assertIn("antithesis-restated-subject", labels_for(text)) + + def test_negative_different_subject(self): + text = "The key is not rotated. The vault is sealed." + self.assertNotIn("antithesis-restated-subject", labels_for(text)) + + def test_negative_no_negation(self): + text = "The goal is a smaller policy. The goal is also cheaper to review." + self.assertNotIn("antithesis-restated-subject", 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 + # Ralph review dropped this label (fixed in rivassec/research-feed). + text = ( + "The problem is not that engineers cannot read JSON. It is that " + "reviewing individual permissions is not the same as reasoning " + "about their consequences." + ) + self.assertIn("antithesis-period-split", labels_for(text)) + def test_classic_form_a(self): text = "It is not a bug, it is a feature." self.assertIn("antithesis-it-not-it-is", labels_for(text))