Skip to content

chore: check artifact name/description for unsupported claim language - #387

Merged
abrignoni merged 1 commit into
mainfrom
chore/check-claim-language
Aug 1, 2026
Merged

chore: check artifact name/description for unsupported claim language#387
abrignoni merged 1 commit into
mainfrom
chore/check-claim-language

Conversation

@abrignoni

Copy link
Copy Markdown
Owner

Ports the check_claim_language.py guard already running in iLEAPP and ALEAPP into RLEAPP, wired into the existing Python Lint Check workflow as an extra step in the existing job.

What it guards

An artifact module's __artifacts_v2__ name and description are not developer notes. They render into the HTML report, get written into the LAVA manifest, and from there they are pasted into examination notes and quoted in court. A description that says an artifact holds "every message the user sent" is a statement about a person's conduct that the underlying return does not make.

The standard for those two fields:

Say what the data is and where it came from. Do not say what it means about the world, or who did it, unless the data itself establishes that or the description cites a source that documents it.

The failure mode the check exists to stop is the partial fix: someone corrects a module's docstring and the description field a few lines above it keeps the original wording, so the claim keeps shipping to reports. Prose fixes go stale exactly where nothing is watching them.

How it works

Parses each module under scripts/artifacts/ with ast, evaluates the __artifacts_v2__ literal, and matches name and description against a vocabulary of phrasing that has historically signalled an unsupported claim: completeness (all, every, complete, entire), attribution of an act to a person (the user viewed, user-created, typed by, manually), and certainty or inference (proves, always, reliable, visited, habits).

The vocabulary is deliberately blunt. It flags wording that is usually a claim, not wording that is always one, so a match is a prompt to look rather than a verdict.

Every alternative is anchored with an explicit \b. Expressing the boundary as a trailing space instead (all ) matches inside "call log" and "install ", which in an earlier draft of this check turned 35 of 37 raw hits into false positives. Word boundaries also keep hedges quiet: neither "unreliable" nor "incomplete" trips.

Result in this repo

164 artifact modules scanned. 155 are statically readable, covering 246 v2 entries. One match, triaged as a legitimate exception; the check exits 0.

The one allowlist entry

takeoutGoogleMail:description"Parses MBOX mailboxes (All Mail, Deleted) from a Google Takeout export, including attachments."

"All Mail" is Gmail's own name for the system label, and Takeout ships it as a file literally called All mail Including Spam and Trash.mbox — which is the glob this artifact searches for in its paths. The word names the mailbox being parsed, not a claim that the parse recovered every message the account ever held. This is directly analogous to the Box "All Files" entry in the iLEAPP allowlist: renaming it would make the artifact harder to match to what the examiner sees in the export. The reasoning is written inline next to the entry.

Allowlist discipline

A match is resolved one of two ways:

  • The wording overstates what the parser can show — reword to what the data shows. Name the table, file, or stream; drop the actor and the completeness word. This is the common case.
  • The match is a false positive — a product feature name, a verbatim schema enum value, a UI path reproduced from the app, or a cautionary sentence whose matched word is part of the hedge. Add a (filename, artifact_key, field) tuple to ALLOWLIST with an inline comment saying why. The allowlist is a record of decisions someone made on purpose; it is not a place to park a description nobody wanted to rewrite.

A stale ALLOWLIST entry — one that no longer matches anything — fails the run. It means the description was reworded or the key changed, and the entry now shields nothing except the next claim that lands under the same key.

Documented coverage gap: 9 dynamic modules

Nine modules build __artifacts_v2__ by calling a local helper (_meta(...), _m(...)) or by referencing a shared note constant (_NOTE, _REGEX_NOTE) rather than writing a literal. Their fields never reach ast.literal_eval, so no claim language in them is checked:

scripts/artifacts/cashappReturns.py
scripts/artifacts/coinbaseArchive.py
scripts/artifacts/discordReturnsOnlineDMs.py
scripts/artifacts/gabPosts.py
scripts/artifacts/kikReturns.py
scripts/artifacts/netflixArchive.py
scripts/artifacts/reddit.py
scripts/artifacts/snapchatAccounthist.py
scripts/artifacts/twitterReturnsTip.py

The run prints this set as NOT CHECKED on every invocation, so the hole cannot go quiet. It is also written into the script's docstring so the gap is a documented fact rather than something a reader has to rediscover from output. The list is not maintained by hand — the run is the authority if the two ever disagree.

Bringing one of these under the check means giving it a literal __artifacts_v2__, which is a behaviour change to the module and belongs in its own pull request.

Verification

  • python3 admin/scripts/check_claim_language.py exits 0 (155 checked, 9 NOT CHECKED, 1 allowlisted).
  • Deliberate break: setting accPinger description to "all records the user viewed" exits 1 and prints scripts/artifacts/accPinger.py:accPinger:description with matched: all, the user viewed. Reverted.
  • --list shows the single allowlisted match; a stale entry is reported and exits 1.
  • python3 -m py_compile clean; admin/scripts/lint_changed.py reports 0 warnings on the new file.
  • admin/test/scripts/ all pass (2 + 2 + 13 tests).
  • Workflow YAML parses; the new step lands between the dependency install and the pylint run.

🤖 Generated with Claude Code

Artifact `name` and `description` are examiner-facing: they render into the
HTML report, get written to the LAVA manifest, and are quoted in casework.
The project standard is to say what the data is and where it came from, not
what it means about the world or who performed an act, unless the data
establishes it or a cited source documents it.

This adds the check already running in iLEAPP and ALEAPP. It parses each
module in scripts/artifacts with `ast`, evaluates the `__artifacts_v2__`
literal, and matches both checked fields against a vocabulary of
completeness, attribution, and certainty phrasing.

Of 164 artifact modules, 155 are statically readable (246 v2 entries) and
one match was triaged: takeoutGoogleMail's "All Mail" is Gmail's own system
label, shipped by Takeout as a file literally named "All mail Including Spam
and Trash.mbox" -- which is the glob the artifact searches for. It names the
mailbox being parsed, not a claim of completeness, so it is allowlisted with
that reasoning inline.

The remaining 9 modules build `__artifacts_v2__` from a helper call or a
shared note constant, so their fields never reach ast.literal_eval and are
never checked. The run prints them as NOT CHECKED every time, and they are
also listed in the script's docstring so the coverage gap is documented
rather than implicit. Giving those modules a literal dict is a behaviour
change to each module and belongs in its own pull request.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@abrignoni
abrignoni merged commit 26d8410 into main Aug 1, 2026
1 check passed
@abrignoni
abrignoni deleted the chore/check-claim-language branch August 1, 2026 05:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant