Harden Austrian swap-link markers#4
Merged
Merged
Conversation
Addresses two classes of silent-underreporting bugs flagged in an adversarial review of the recent AT swap-neutrality work: 1. Loose `notes` parsing. Marker detection used substring matching, so unrelated free-form text (e.g. `prefixed_at_swap_link=foo`) could flip the regime or trigger Neu swap neutrality by accident. Now tokenizes on the documented separators (` \t\n,`) and matches exact tokens only. Duplicate/conflicting markers raise instead of resolving to the first match. 2. No cross-asset validation. A missing incoming leg on a crypto-to-crypto swap (partial wallet export, Kassiber emission bug) would silently produce a zero-gain disposal with no basis carry anywhere. Adds an `AbstractCountry.validate_input_data` hook (default no-op, upstream- friendly) invoked from rp2_main before per-asset accounting. `AT` overrides to run `validate_at_swap_link_pairing`, which asserts every `at_swap_link=<id>` appears on exactly one outgoing and one incoming leg on two different assets. Events tagged `at_regime=alt` are skipped (Alt swaps are regime-breaking per § 27b EStG — the marker is a no-op there). `rp2_main` now parses every configured asset for validation, then restricts accounting/reporting to `--asset` if set, so narrowed diagnostic runs don't misreport valid paired swaps as orphaned. Also adds a same-event kind check: `at_swap_link=` on a non-SELL Neu disposal (GIFT/DONATE/FEE/LOST/STAKING) raises, since swap neutrality is the § 27b Abs 3 Z 2 EStG *sale* carveout and has no pairable incoming leg for non-sale dispositions. AGENTS.md updated to document tokenized matching, duplicate/conflict rejection, the SELL-only constraint, and the new pairing guarantee. The "RP2 does not perform cross-asset validation" language is replaced with the new contract; the Known Gaps entry now correctly identifies basis-*value* verification (still trusted) as the remaining gap, not basis-*pairing* (now enforced). 237 tests pass, mypy clean, pylint 10/10, bandit clean, pre-commit passes. Smoke-tested with and without `--asset` on the sample dataset. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two classes of silent-underreporting bugs surfaced by an adversarial review of the recent Austrian swap-neutrality work are addressed here.
notesmarker detection switched from substring matching to exact-token matching on the documented separators (\t\n,). Unrelated text likeprefixed_at_swap_link=foono longer flips the regime or triggers Neu swap neutrality. Duplicate/conflicting markers raiseRP2ValueErrorinstead of silently resolving to the first match.AbstractCountry.validate_input_datahook (default no-op; upstream-friendly) called fromrp2_mainbefore per-asset accounting.AToverrides to runvalidate_at_swap_link_pairing, which asserts everyat_swap_link=<id>appears on exactly one outgoing and one incoming leg across two different assets — catching the class of bug Kassiber structurally cannot catch (a leg that never made it into its input at all, e.g. partial wallet export). Events taggedat_regime=altare skipped (Alt swaps are regime-breaking per § 27b EStG; the marker is a no-op there).rp2_mainnow parses every configured asset for validation, then restricts accounting/reporting to--assetif set, so narrowed diagnostic runs don't misreport valid paired swaps as orphaned.at_swap_link=on a non-SELL Neu disposal (GIFT/DONATE/FEE/LOST/STAKING) now raises — swap neutrality is the § 27b Abs 3 Z 2 EStG sale carveout and has no pairable incoming leg for non-sale dispositions.AGENTS.md is updated to document tokenized matching, duplicate/conflict rejection, the SELL-only constraint, and the new pairing guarantee. The Known Gaps entry now correctly identifies basis-value verification (still trusted to Kassiber) as the remaining gap rather than basis-pairing (now enforced on RP2's side).
Test plan
mypy src testsclean.pylint -r nrated 10.00/10 on changed files.bandit -r srcclean.pre-commit runpasses on all changed files.rp2_at -o /tmp/... config/crypto_example.ini input/crypto_example.odswith and without-a BTC.🤖 Generated with Claude Code