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
37 changes: 6 additions & 31 deletions admin/scripts/check_claim_language.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,24 +122,9 @@
# habitat-related artifact ever lands here, close the stem rather than
# allowlisting the artifact.
CLAIM_PATTERN = re.compile(
r'\ball\b'
r'|\bevery\b'
r'|\bcomplete'
r'|\bfull list\b'
r'|\bentire\b'
r'|\bthe user (?:searched|typed|viewed|visited|opened|selected|deleted|read|sent'
r'|created|hid|chose)\b'
r'|\buser[- ](?:created|entered|typed|searched|selected|initiated)\b'
r'|\b(?:searched|typed|viewed|read|entered|created|sent|opened|selected|delet'
r'ed|visited|chosen|hidden|initiated) by (?:the |a |an )?(?:user|account holder|device owner|subject|owner)\b'
r'|\bmanually\b'
r'|\bproves?\b'
r'|\bdefinitively\b'
r'|\balways\b'
r'|\breliable'
r'|\bvisited\b'
r'|\bhabits?\b',
re.IGNORECASE)
r"\ball\b|\bevery\b|\bcomplete|\bfull list\b|\bentire\b|\b(?:the|a|an) (?:\w+ )?(?:user|account holder|device owner|subject|owner) (?:searched|typed|viewed|read|entered|created|sent|opened|selected|deleted|visited|chose|chosen|hid|hidden|initiated|follows|saved|added|grouped|submitted|marked|tracked|joined|reacted|installed|removed|configured|voted|opted)\b|\buser[- ](?:created|entered|typed|searched|selected|initiated)\b|\b(?:searched|typed|viewed|read|entered|created|sent|opened|selected|deleted|visited|chosen|hidden|initiated) by (?:the |a |an )?(?:user|account holder|device owner|subject|owner)\b|\bmanually\b|\bproves?\b|\bdefinitively\b|\balways\b|\breliable|\bvisited\b|\bhabits?\b",
re.IGNORECASE,
)

# `notes` reaches the examiner too, in the report and in the artifact info modal, so
# the same standard applies to it. It cannot use the same vocabulary, because notes do
Expand All @@ -156,19 +141,9 @@
# What is left is attribution and certainty, which mean the same thing in a note as in
# a description.
NOTES_PATTERN = re.compile(
r'\bthe user (?:searched|typed|viewed|visited|opened|selected|deleted|read|sent'
r'|created|hid|chose)\b'
r'|\buser[- ](?:created|entered|typed|searched|selected|initiated)\b'
r'|\b(?:searched|typed|viewed|read|entered|created|sent|opened|selected|delet'
r'ed|visited|chosen|hidden|initiated) by (?:the |a |an )?(?:user|account holder|device owner|subject|owner)\b'
r'|\bmanually\b'
r'|\bproves?\b'
r'|\bdefinitively\b'
r'|\balways\b'
r'|\breliable'
r'|\bvisited\b'
r'|\bhabits?\b',
re.IGNORECASE)
r"\b(?:the|a|an) (?:\w+ )?(?:user|account holder|device owner|subject|owner) (?:searched|typed|viewed|read|entered|created|sent|opened|selected|deleted|visited|chose|chosen|hid|hidden|initiated|follows|saved|added|grouped|submitted|marked|tracked|joined|reacted|installed|removed|configured|voted|opted)\b|\buser[- ](?:created|entered|typed|searched|selected|initiated)\b|\b(?:searched|typed|viewed|read|entered|created|sent|opened|selected|deleted|visited|chosen|hidden|initiated) by (?:the |a |an )?(?:user|account holder|device owner|subject|owner)\b|\bmanually\b|\bproves?\b|\bdefinitively\b|\balways\b|\breliable|\bvisited\b|\bhabits?\b",
re.IGNORECASE,
)

# A note that *denies* a claim uses the same words as one that makes it: "not terms the
# user searched for", "does not establish that the user viewed them". That denial is the
Expand Down
59 changes: 44 additions & 15 deletions admin/test/scripts/test_check_claim_language.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,23 +36,10 @@

# The vocabulary every core is expected to enforce, written out rather than imported.
EXPECTED_CLAIM = (
r'\ball\b|\bevery\b|\bcomplete|\bfull list\b|\bentire\b|'
r'\bthe user (?:searched|typed|viewed|visited|opened|selected|deleted|'
r'read|sent|created|hid|chose)\b|\buser[- ](?:created|entered|typed|'
r'searched|selected|initiated)\b|\b(?:searched|typed|viewed|read|'
r'entered|created|sent|opened|selected|deleted|visited|chosen|hidden|'
r'initiated) by (?:the |a |an )?(?:user|account holder|device owner|'
r'subject|owner)\b|\bmanually\b|\bproves?\b|\bdefinitively\b|'
r'\balways\b|\breliable|\bvisited\b|\bhabits?\b'
r"\ball\b|\bevery\b|\bcomplete|\bfull list\b|\bentire\b|\b(?:the|a|an) (?:\w+ )?(?:user|account holder|device owner|subject|owner) (?:searched|typed|viewed|read|entered|created|sent|opened|selected|deleted|visited|chose|chosen|hid|hidden|initiated|follows|saved|added|grouped|submitted|marked|tracked|joined|reacted|installed|removed|configured|voted|opted)\b|\buser[- ](?:created|entered|typed|searched|selected|initiated)\b|\b(?:searched|typed|viewed|read|entered|created|sent|opened|selected|deleted|visited|chosen|hidden|initiated) by (?:the |a |an )?(?:user|account holder|device owner|subject|owner)\b|\bmanually\b|\bproves?\b|\bdefinitively\b|\balways\b|\breliable|\bvisited\b|\bhabits?\b"
)
EXPECTED_NOTES = (
r'\bthe user (?:searched|typed|viewed|visited|opened|selected|deleted|'
r'read|sent|created|hid|chose)\b|\buser[- ](?:created|entered|typed|'
r'searched|selected|initiated)\b|\b(?:searched|typed|viewed|read|'
r'entered|created|sent|opened|selected|deleted|visited|chosen|hidden|'
r'initiated) by (?:the |a |an )?(?:user|account holder|device owner|'
r'subject|owner)\b|\bmanually\b|\bproves?\b|\bdefinitively\b|'
r'\balways\b|\breliable|\bvisited\b|\bhabits?\b'
r"\b(?:the|a|an) (?:\w+ )?(?:user|account holder|device owner|subject|owner) (?:searched|typed|viewed|read|entered|created|sent|opened|selected|deleted|visited|chose|chosen|hid|hidden|initiated|follows|saved|added|grouped|submitted|marked|tracked|joined|reacted|installed|removed|configured|voted|opted)\b|\buser[- ](?:created|entered|typed|searched|selected|initiated)\b|\b(?:searched|typed|viewed|read|entered|created|sent|opened|selected|deleted|visited|chosen|hidden|initiated) by (?:the |a |an )?(?:user|account holder|device owner|subject|owner)\b|\bmanually\b|\bproves?\b|\bdefinitively\b|\balways\b|\breliable|\bvisited\b|\bhabits?\b"
)
EXPECTED_NEGATION = (
r'\b(not|no|never|nor|neither|without|cannot|rather than|instead of|'
Expand Down Expand Up @@ -258,6 +245,48 @@ def test_the_two_vocabularies_agree_on_the_passive_family(self):
self.assertEqual(bool(ccl.CLAIM_PATTERN.search(probe)),
bool(ccl.NOTES_PATTERN.search(probe)))

class ActiveFormsAreAnchoredOnAPerson(unittest.TestCase):
"""The active family must fire on a person subject however it is worded, and stay
silent when the subject is not a person.

Anchored only on the literal "the user", the check was evadable by naming the app in
front of the noun ("the Flipboard user follows", "the Hinge account holder selected")
or by a different determiner ("a user tracked"), with nothing about the attribution
changed. The subject now takes the same determiners and person-noun set as the
passive family, plus an optional descriptor, and the verb set adds the conduct words
that recur in these descriptions.
"""

PERSON_SUBJECTS = ('the user', 'a user', 'the Flipboard user',
'the Hinge account holder', 'the device owner', 'an account owner')
CONDUCT = ('follows', 'saved', 'added', 'grouped', 'submitted', 'marked',
'tracked', 'joined', 'reacted', 'selected', 'typed', 'visited')

def test_a_person_subject_fires_however_worded(self):
for subject in self.PERSON_SUBJECTS:
for field in ('description', 'notes'):
with self.subTest(subject=subject, field=field):
self.assertTrue(fires(field, f'items {subject} added to the list'))

def test_each_conduct_verb_fires_after_a_person(self):
for verb in self.CONDUCT:
for field in ('description', 'notes'):
with self.subTest(verb=verb, field=field):
self.assertTrue(fires(field, f'things the user {verb}'))

def test_a_non_person_subject_in_the_active_voice_stays_silent(self):
for text in ('the app added a row', 'the parser opened the file',
'the sync service created a record', 'the indexer marked it read',
'the calling app submitted the query'):
for field in ('description', 'notes'):
with self.subTest(text=text, field=field):
self.assertFalse(fires(field, text))

def test_the_motivating_case_is_caught(self):
# ALEAPP #1332 corrected this description; the anchoring is what stops the class.
self.assertTrue(fires('description',
'Topics and magazines the Flipboard user follows'))


if __name__ == '__main__':
unittest.main()
2 changes: 1 addition & 1 deletion scripts/artifacts/instagramNointerest.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
__artifacts_v2__ = {
"instagramNointerest": {
"name": "Instagram Archive - Accounts No Interest",
"description": "Parses accounts the user marked as not interested from an Instagram data archive",
"description": "Parses accounts marked as not interested from an Instagram data archive",
"author": "@AlexisBrignoni",
"creation_date": "2021-08-27",
"last_update_date": "2026-06-27",
Expand Down
Loading