| Age | Commit message (Collapse) | Author | Files | Lines |
|
Backlog item 4 asked for a primary source for the machine-readable part.
It exists, and it is IANA's MARF registry rather than x-arf's own docs.
Checked against that registry and RFC 5965 itself on 2026-09-10.
Report-Type is not a registered field. The registry lists 25 names and it
is absent; it came from this spec's worked example rather than a decision.
Removed, because Feedback-Type already says it. Keeping it would have been
harmless, since RFC 5965 section 6 makes ignoring an unknown field a MUST
for the receiver, but that same section requires an extension field be
registered, and a desk should be able to look up every field in a document
this tool sends.
Feedback-Type moves from abuse to fraud. RFC 5965 registers fraud as
"indicates some kind of fraud or phishing activity" and abuse as
"unsolicited email or some other kind of email abuse". This tool reports
phishing, and some desks route fraud separately from bulk spam.
Reported-Uri becomes Reported-URI, the spelling in the ABNF and the
registration. The RFC's own worked example uses the other one, which is
where this came from. Names are case-insensitive so nothing was broken.
A new test holds the registry's 25 names and asserts every emitted field
is one of them, with Source named as the single deliberate exception, so
a new unregistered field cannot arrive unnoticed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LByBnw83xr9YP85nskzkyE
|
|
Refuses without a configured reporter identity rather than filing a report
with no reply address, and turns a frozen or edited case into an error
message rather than a traceback.
The identity rule is EMAIL REQUIRED, NAME AND ORG OPTIONAL, not the plan's
"all three required". Each [reporter] key is individually skippable at init
and config drops a skipped one rather than storing "", so a partial identity
is a normal shape; text_part() already renders whatever subset is present.
Requiring all three would refuse a config init itself writes without
complaint. The address is different in kind because it becomes the From.
That closes backlog item 5: report.build() read identity["email"] directly
and raised a bare KeyError on a name-without-address identity, which is
exactly what skipping one init prompt while answering another produces. The
check lives in the command rather than the builder, since that is what
decides whether the user sees an exit code and a sentence or a traceback.
Two further defects in the plan's code, both fixed here:
- It called config.load() with no argument, so the global --config option
was silently ignored for this subcommand alone. It now resolves the path
the way _cmd_parse does. Reporting against the wrong identity is the
failure that would have caused.
- Its summary printed counts and no path. Nothing sends these bodies yet, so
reviewing them by hand is the entire point of the command, and the case's
bodies directory is now named in the output.
case.py stays the only writer of the manifest: generate() returns it and
case.save() writes it atomically. Bodies are written before that save, so a
failed save leaves bodies the manifest does not record; the next run finds no
recorded hash, treats them as regenerable rather than hand-edited and
overwrites them, and nothing has been sent, so no machinery is warranted.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LByBnw83xr9YP85nskzkyE
|
|
Each answer is validated at the prompt that asked for it, and a skipped
answer is absent from the file rather than an empty string.
Two defects fixed beyond the plan, both in the carry-across rule. Sections
build() does not produce are preserved verbatim, and that rule was written
when [general] was the only section it produced. Emitting [reporter] as well
made preservation emit it TWICE, and tomllib refuses a duplicate table
outright, so the rewritten file became unreadable and took the preserved
[misp] key with it. Dropping [reporter] unconditionally instead would have
been the opposite defect: an init that skips all three questions would
silently delete an identity set by hand. What is dropped is now read back off
the rendered text, so it is what this run actually wrote rather than what it
might have written, and a section added to the builder later cannot be
forgotten here.
The email check is deliberately not an RFC 5322 validator. What a typo costs
is a report whose reply address bounces, and the answers that produce that
are a name with no @ at all, a spelled-out "at", and a stray space from a
copy-paste. Anything stricter starts rejecting addresses that work.
Skipping the address is allowed but warned about at the prompt: it is the one
field a report cannot be built without, since it becomes the From.
report.build() raises a bare KeyError on that identity today, which the three
prompts made reachable from a config file for the first time; logged as
backlog item 5 rather than fixed here, because where the check belongs is the
report subcommand.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LByBnw83xr9YP85nskzkyE
|
|
Every other field in the machine-readable part was checked against RFC 5965
itself. This one follows the design document's worked example and no primary
x-arf source was reachable to confirm it, so it is recorded rather than left
as an assumption in the code.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xj1ayFRSUQ2u7cwb3S4axE
|
|
The author keeps ideas in an Obsidian note outside this repository. Nothing
in the repo pointed at it, so a fresh session would never find it and the
backlog would look complete while ideas sat unread. AGENTS.md now names the
path and the rule: the note is the inbox, the backlog is the tracked list,
and they are reconciled in both directions.
Item 3 is that note's one open idea, an API letting qtmaildir tag incoming
mail by resemblance to a kept case. It is recorded unsized and explicitly
marked as having no verified cause, because unlike items 1 and 2 there is
nothing built to verify. The entry records the real tension rather than just
the request: this tool has been deliberately mechanical, reporting what a
message declared and refusing rather than guessing, and a resemblance score
would be its first opinion. It also notes that source.eml is unredacted, so
matching must not become a route by which a stored recipient identifier
reaches a comparison.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wrfqr2xqQfhtXCscU7zrdz
|
|
An RDAP query discloses what the user is looking at, and property 1
covers only what is published, so the query rule needed stating in its
own right beside the other three. The section records the three leaks it
came from, because each one was the same shape: validation applied per
branch, forgotten on the next.
Also moves rdap.py's imports into one block at the top. Pure move, the
suite is 192 either side of it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wrfqr2xqQfhtXCscU7zrdz
|
|
A synthetic fixture only ever holds the shapes someone thought of, and
the shapes nobody thought of are the ones that leak. Sweeping the user's
own tag:spam is the only source of those, and it earned its place: the
first message found a header the parser did not read, and the corpus
exercised a spoofed Reply-To display name no fixture had.
AGENTS.md records it under Testing, beside the fixture rule it is the
deliberate exception to: ask first, work from the scratchpad, compare
every address in the raw source against the whole ioc output, and let
only counts and stripped domains out. A finding becomes a synthetic
fixture; the real message never enters the repository.
BACKLOG.md opens with the one thing the sweep found that was not worth
fixing blind: boilerplate namespace urls (w3.org doctypes) reported as
indicators. Noise rather than a defect, and the argument for leaving it
alone is real, so it is written down with both sides rather than built.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019NHaqA1Rz5ybed7wFUeQbK
|